Along with numerous new developer features and updates brought to us in WWDC21, including Swift updates like the async/await syntax, structured concurrency, and actors, Apple provided new features, enhancements, and worthwhile deprecations to SwiftUI, which continue to bolster the declarative UI framework as a viable and advantageous option for UI development.
Updates like the task modifier nicely link SwiftUI to the concurrency updates to Swift, while many other updates serve more so as “quality of life” improvements for the typical SwiftUI developer. Often times these improvements offer more convenient, native SwiftUI support for views that were previously only achievable with UIKit and UIViewRepresentable. Without further ado, let’s jump into some of these updates.
The @FocusState property wrapper allows us to track the current focus position in the view hierarchy. This property wrapper can be bound to a Boolean to control one single input field, or to an enum to control movement between several. We can both read and write to this property, meaning we can finally control the focus state of a TextField, as well as any other focusable View type, from our code.
In the above code, we are able to toggle the focus state of our one TextField by tapping on the Button. And here is the result:
But what if we had multiple TextFields in a form? In this case we can bind our focus state to an enum to dictate focus between those different fields.
Notice a couple additional new SwiftUI updates in this code. The submitLabel view modifier assigns the label of the return key of the presented keyboard for the corresponding TextField. The submission action triggered by tapping on this key is then handled with the onSubmit modifier. With focus state and onSubmit, we can now handle focus state changes between multiple TextFields in the same view hierarchy.
Here is what the result looks like:
Focus State is a very welcome new feature to SwiftUI which addresses a pretty glaring limitation of TextField from the previous releases of SwiftUI that led many to rely on UITextField with a UIViewRepresentable. With focus state, TextField is much more viable for many who whave specific focus related requirements to their input fields.
Running asynchronous code in SwiftUI
For many uses, the updates to Swift concurrency require we have a way to perform asynchronous code when a SwiftUI view appears. Trying to perform any asynchronous code in onAppear will give us a compiler error saying:
To solve this, we can instead use the task modifier to perform this asynchronous code.
You can also use the task(id:priority:_:) definition to execute asynchronous code when an observed value changes.
We can now load remote images via AsyncImage. Being able to show images from remote URLs was another common need not previously natively supported with SwiftUI.
At the most basic use case, we can instantiate an AsyncImage with a url like so:
We can also instantiate an AsyncImage using this initializer, which allows us to pass in two ViewBuilder closures: one to manipulate the resulting Image, and one to define a placeholder view to show while the remote image is loading.
List received a number of welcome updates that continue to strengthen it as a viable alternative to UITableView.
To build out this example I used NewsAPI to get a list of trending news articles in the United States.
The swipeActions() modifier allows us to add swipe buttons to a row in a List. This is an example of something that was pretty common in many UITableViews which List did not previously support natively.
The modifier takes a ViewBuilder closure to define the view shown in the row when swiped. You can also optionally specify the edge of the row to display the swipe action button (defaulted to the trailing edge), and whether or not a full swipe should automatically perform the first action.
In my News Articles example, my swipe action will either be “Favorite” to add the News Article as a “favorite” article, or “Trash” to remove the article as a “favorite”.
The refreshable() modifier allows us to easily attach “pull to refresh” functionality to a List. refreshable() takes an asynchronous action handler that executes when the user requests a refresh.
The searchable() modifier allows us to place a search bar directly in a NavigationView, and can be used to filter data in a List.
The searchable() modifier takes a binding to text to display and edit in the search field. It is up to us to filter the items of our List accordingly.
Here is our final view code, with searchable() applied to the NavigationView.
A Noteworthy Animation Deprecation
.animation(_: ) has been deprecated. This modifier previously caused unexpected behaviors because it would cause the animation to re-trigger any time state changed.
Instead, use .animation(_: value: ) to attach your animation to a specific value changing.
In the following example, I am animating the disabled state of the “Favorite” and “Trash” buttons changing. When the news article is already favorited, “Favorite” is disabled. If the news article is not yet favorited, the “Trash” button is disabled.
Here is the resulting animation:
In this post, we’ve looked at some of the updates made to SwiftUI with its 3.0 release, and we’ve just scratched the surface. Additional updates and enhancements include more modifiers for customizing the appearance of Lists, new button styles, markdown support for Text, Material views to apply blur effects to background views, and more.
With each subsequent release of SwiftUI, Apple’s declarative UI framework continues to grow more and more robust with features that assert its place as a viable and advantageous UI framework of choice for iOS developers worldwide. It will be exciting to see how Apple continues to build upon these improvements and SwiftUI at large over time.