Visual effects, remote URLs, and cleaned up APIs galore.
Expectations were always going to be high for SwiftUI this year, but the team didn’t disappoint – they’ve shipped a massive collection of improvements and features, including a new AsyncImage
view for loading remote images, swipe actions for list rows, pull to refresh, plus shorter, simpler APIs for common uses. Alongside huge improvements to Swift itself (see What's new in Swift 5.5 for more on that), this is another significant leap forward for SwiftUI and I’m really keen to dive in.
Please keep in mind that these changes are very new – I'm pushing it as hard as I can, experimenting, refining, sharing, and learning all at the same time. If you have any feedback, please tweet me @twostraws.
You can watch the video below, or scroll down for links to articles.
SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until October 1st.
Sponsor Hacking with Swift and reach the world's largest Swift community!
The one-parameter form of the animation()
modifier has now been formally deprecated, mostly because it caused all sorts of unexpected behaviors. To see the problem yourself, try code like this:
struct ContentView: View {
@State private var scaledUp = true
var body: some View {
VStack {
Text("Hello, world!")
.scaleEffect(scaledUp ? 2 : 1)
.animation(.linear(duration: 2))
.onTapGesture { scaledUp.toggle() }
}
}
}
That seems simple enough: animation the text getting larger or smaller when it’s tapped. However, we attached the animation to everything for the label, which means even something like rotating the screen would make the text label animate from its old position to its new position.
The solution here is to use animation(_:value:)
to attach your animation to a specific value changing, like this:
struct ContentView: View {
@State private var scaledUp = true
var body: some View {
VStack {
Text("Hello, world!")
.scaleEffect(scaledUp ? 2 : 1)
.animation(.linear(duration: 2), value: scaledUp)
.onTapGesture { scaledUp.toggle() }
}
}
}
iOS 15 provides a middle ground between Button
and Toggle
, which is a Toggle
that looks like a button but flips its foreground and background colors in its on state. This is enabled using the .button
toggle style, as shown in the following code:
struct ContentView: View {
@State private var isOn = false
var body: some View {
Toggle("Filter", isOn: $isOn)
.toggleStyle(.button)
.tint(.mint)
}
}
In iOS 15 SwiftUI now automatically selects the correct variant of an SF Symbols icon when used inside a TabView
.
According to the iOS human interface guidelines icons ought to be filled when used inside a TabView
, but according to the macOS human interface guidelines they should be stroked. To make this work well on both platforms, you can now specify the simple, unfilled form of the image and have SwiftUI use the correct variant as appropriate for the platform.
So, on iOS this will show the filled forms of each of our icons:
TabView {
Text("View 1")
.tabItem {
Label("Home", systemImage: "house")
}
Text("View 2")
.tabItem {
Label("Account", systemImage: "person")
}
Text("View 3")
.tabItem {
Label("Community", systemImage: "theatermasks")
}
}
In iOS 15 menus can also have a primary action attached, which is triggered when the menu’s button is tapped rather than held down. So, you press and release to trigger the primary action, or hold down to get the full menu of options.
We could create a menu that supports both simple taps as well as a full set of options:
struct ContentView: View {
var body: some View {
Menu("Options") {
Button("Order Now", action: placeOrder)
Button("Adjust Order", action: adjustOrder)
Button("Cancel", action: cancelOrder)
} primaryAction: {
justDoIt()
}
}
func justDoIt() {
print("Button was tapped")
}
func placeOrder() { }
func adjustOrder() { }
func cancelOrder() { }
}
There’s a newer, simpler way to dismiss views programmatically. Rather than using the presentationMode
environment key then calling presentationMode.wrappedValue.dismiss()
, you should instead use the new .dismiss)
environment key:
@Environment(\.dismiss) var dismiss
That uses Swift’s callAsFunction()
, so with that in place you can now make a sheet dismiss itself by calling dismiss()
.
There's a new format
parameter when creating Text
views, allowing you to format the value as a list, as a percentage, as a measurement, and more. You can read about this change here: How to format text inside text views
iOS 15 lets you attach a role to your button to help SwiftUI know what kind of styling should be attached to the button. For example, if we had a Delete button we might mark it with the .destructive
role so SwiftUI can highlight it in red when it makes sense:
Button("Delete", role: .destructive) {
print("Perform delete")
}
Plus, there are stacks of improvements that have come about thanks to improvements in Swift itself. You can find out more in my article What’s New in Swift 5.5, but here’s the abridged version.
First, styles now use much simpler names: SidebarListStyle()
becomes just .sidebar
, and RoundedBorderTextFieldStyle()
becomes just .roundedBorder
.
Second, you can now use #if
for postfix member expressions, like this:
Text("Welcome")
#if os(iOS)
.font(.largeTitle)
#else
.font(.headline)
#endif
And third, Swift 5.5 is able to implicitly convert between CGFloat
and Double
in most places where it is needed, which means you can pretty much remove CGFloat
from your SwiftUI code.
Last year was huge for SwiftUI, but this year the framework is really maturing – we’re seeing old UIKit controls such as UIVisualEffectView
and UISearchController
being dramatically rethought for a SwiftUI world, and the results are pretty darn incredible.
Thanks to the SwiftUI team for doing such an incredible job this year, and to Apple’s developer publications team for working so hard to create fantastic documentation we can all learn from!
SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until October 1st.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.