WWDC was again in individual for the primary time since 2019, and as soon as once more was completely filled with new options for app builders – huge new features in Swift 5.7, new APIs reminiscent of WeatherKit and Swift Charts, and, in fact, a number of goodies for SwiftUI builders.
Please understand that these modifications are very new – I am pushing it as arduous as I can, experimenting, refining, sharing, and studying all on the similar time. You probably have any suggestions, please tweet me @twostraws. I’ll be including extra quickly!
You’ll be able to watch the video under, or scroll down for hyperlinks to articles.
Sponsor Hacking with Swift and reach the world’s largest Swift community!
The large stuff
Further: It’s now attainable to create multi-column tables on iOS and iPadOS, however I believe it’s a bit buggy in beta 1. I’ve documented it right here in case you’d prefer to attempt it your self: How to create multi-column lists using Table.
Extra welcome enhancements
And there’s extra
This launch comes with a complete batch of small tweaks, fixes, and enhancements to make the SwiftUI expertise smoother throughout.
First, you may get a superbly easy linear gradient by appending .gradient
to no matter shade you’re utilizing:
Rectangle().fill(.blue.gradient)
This works with any shade, and is at all times simply the smallest trace of a gradient to assist deliver your UI to life:
struct ContentView: View
let colours: [Color] = [.blue, .cyan, .green, .yellow, .orange, .red, .purple]
var physique: some View
VStack
ForEach(colours, id: .self) shade in
Rectangle().fill(shade.gradient)
There’s new API for creating shadows for shapes, each common drop shadows:
Circle()
.fill(.pink.shadow(.drop(shade: .black, radius: 10)))
.padding()
And in addition now internal shadows:
Circle()
.fill(.pink.shadow(.internal(shade: .black, radius: 10)))
.padding()
The lineLimit()
modifier has been upgraded to deal with ranges in addition to easy integers:
Textual content("That is some longer textual content that's restricted to a selected vary of traces, so something greater than six traces will trigger the textual content to clip.")
.lineLimit(3...6)
.body(width: 200)
Some modifiers have been upgraded to assist parameters, to allow them to be adjusted at runtime:
struct ContentView: View
@State personal var useBold = false
@State personal var useItalic = false
var physique: some View
VStack
Textual content("Welcome SwiftUI 4.0")
.daring(useBold)
.italic(useItalic)
Toggle("Use Daring", isOn: $useBold)
Toggle("Use Italic", isOn: $useItalic)
.padding()
.font(.title)
We are able to animate all kinds of textual content traits simply through the use of a plain withAnimation()
name, even in methods you would possibly suppose unimaginable. For instance, we will animate the burden of a font from very skinny to very thick:
struct ContentView: View
@State personal var useBlack = false
var physique: some View
Textual content("Hey, world!")
.font(.largeTitle)
.fontWeight(useBlack ? .black : .ultraLight)
.onTapGesture
withAnimation
useBlack.toggle()
Subsequent, we will now create textual content fields that robotically develop with their content material, optionally turning them into scrolling views after they go a line restrict:
struct ContentView: View
@State personal var bio = ""
var physique: some View
TextField("Enter your bio", textual content: $bio, axis: .vertical)
.textFieldStyle(.roundedBorder)
.lineLimit(...5)
.padding()
Shifting on, it’s now attainable to bind a Toggle
to an array of Booleans, which is useful for occasions while you need to allow or disable a number of values suddenly. For instance, we may write some code to let the consumer subscribe to particular person newsletters, or have one toggle to modify all of them:
struct EmailList: Identifiable
var id: String
var isSubscribed = false
struct ContentView: View {
@State personal var lists = [
EmailList(id: "Monthly Updates", isSubscribed: true),
EmailList(id: "News Flashes", isSubscribed: true),
EmailList(id: "Special Offers", isSubscribed: true)
]
var physique: some View
Kind
Part
ForEach($lists) $listing in
Toggle(listing.id, isOn: $listing.isSubscribed)
Part
Toggle(isOn: $lists.map(.isSubscribed))
Textual content("Subscribe to all")
}
It’s eventually now attainable to animate foregroundColor()
:
struct ContentView: View
@State personal var useRed = false
var physique: some View
Textual content("WWDC22")
.font(.largeTitle.daring())
.foregroundColor(useRed ? .pink : .blue)
.onTapGesture
withAnimation
useRed.toggle()
And one last item earlier than we transfer onto the most important change of all: SwiftUI for iOS 16 leans closely on a brand new Transferable
protocol for sending information across the system. This can be a newer, less complicated, Swiftier substitute for NSItemProvider
, and also you’ll see it utilized by PasteButton
, ShareLink
, drag and drop, and extra.
NSItemProvider
was by no means a contented slot in any Swift app, by no means thoughts SwiftUI apps, so it’s nice to see a considerably smarter strategy are available – Transferable
is one to observe, for positive.
Goodbye NavigationView, hi there NavigationStack and NavigationSplitView
Most likely the most important change on this launch is a slightly dramatic rethink of navigation in SwiftUI: NavigationView
has been smooth deprecated, and is changed by two new container views relying on what you’re making an attempt to attain.
The less complicated choice of the 2 is known as NavigationStack
, and it’s the equal of utilizing an outdated NavigationView
with the navigation model of .stack
. In truth, you may simply exchange one with the opposite and get precisely the identical consequence:
struct ContentView: View {
var physique: some View
NavigationStack
Record(1..<50) i in
NavigationLink
Textual content("Element (i)")
label:
Label("Row (i)", systemImage: "(i).circle")
.navigationTitle("Navigation")
}
Nevertheless, what the brand new NavigationStack
permits is for us to separate the navigation vacation spot from the navigation hyperlink – to connect that as a separate modifier, like this:
struct ContentView: View {
var physique: some View
NavigationStack
Record(1..<50) i in
NavigationLink(worth: i)
Label("Row (i)", systemImage: "(i).circle")
.navigationDestination(for: Int.self) i in
Textual content("Element (i)")
.navigationTitle("Navigation")
}
So, you may see the NavigationLink
is accountable for setting a price for no matter row quantity was tapped, and navigationDestination()
is accountable for studying that again out and displaying a element view.
That may appear little totally different from what we had earlier than, however this new strategy to navigation makes it a lot simpler to create deep hyperlinks, to deal with state restoration, and in addition to leap to arbitrary locations in our navigation – maybe to push a number of views without delay, or to pop all our views and return to the basis.
For instance, we may pre-select some particular rows by creating a brand new @State
property, then go that into the NavigationStack
as its path:
struct ContentView: View {
@State personal var presentedNumbers = [1, 4, 8]
var physique: some View
NavigationStack(path: $presentedNumbers)
Record(1..<50) i in
NavigationLink(worth: i)
Label("Row (i)", systemImage: "(i).circle")
.navigationDestination(for: Int.self) i in
Textual content("Element (i)")
.navigationTitle("Navigation")
}
That strategy works nice when you’ve gotten solely integers, however a part of the magic of NavigationStack
is that you could connect a number of navigation locations to deal with numerous sorts of information being chosen. On this case, your path must be NavigationPath
slightly than a selected array of knowledge – that is sort erased, so it’s capable of deal with a mixture of information sorts relying on what you push onto the stack.
Right here’s how that appears:
struct ContentView: View {
@State personal var presentedNumbers = NavigationPath()
var physique: some View
NavigationStack(path: $presentedNumbers)
NavigationLink(worth: "Instance")
Textual content("Faucet Me")
Record(1..<50) i in
NavigationLink(worth: i)
Label("Row (i)", systemImage: "(i).circle")
.navigationDestination(for: Int.self) i in
Textual content("Element (i)")
.navigationDestination(for: String.self) i in
Textual content("String Element (i)")
.navigationTitle("Navigation")
}
So, NavigationStack
is a superb alternative while you need views being pushed and popped onto a single view. Should you’re in search of multi-column layouts, it is best to use NavigationSplitView
as a substitute, which helps you to mark precisely what ought to go the place in your break up format.
For instance, we will get a easy two-column format with a listing and element view like this:
struct ContentView: View
@State personal var gamers = ["Dani", "Jamie", "Roy"]
@State personal var selectedPlayer: String?
var physique: some View
NavigationSplitView
Record(gamers, id: .self, choice: $selectedPlayer, rowContent: Textual content.init)
element:
Textual content(selectedPlayer ?? "Please select a participant.")
You can too get a three-column format by including one other trailing closure:
struct Group: Identifiable, Hashable
let id = UUID()
var title: String
var gamers: [String]
struct ContentView: View
@State personal var groups = [Team(name: "AFC Richmond", players: ["Dani", "Jamie", "Row"])]
@State personal var selectedTeam: Group?
@State personal var selectedPlayer: String?
var physique: some View
NavigationSplitView
Record(groups, choice: $selectedTeam) crew in
Textual content(crew.title).tag(crew)
.navigationSplitViewColumnWidth(250)
content material:
Record(selectedTeam?.gamers ?? [], id: .self, choice: $selectedPlayer) participant in
Textual content(participant)
element:
Textual content(selectedPlayer ?? "Please select a participant.")
.navigationSplitViewStyle(.prominentDetail)
I snuck in a few further modifiers in there so you may see extra of what NavigationSplitView
can do:
- I’ve used
navigationSplitViewColumnWidth()
to specify that the sidebar must be precisely 250 factors large. - And I’ve used
navigationSplitViewStyle(.prominentDetail)
to inform SwiftUI to attempt to maintain the element view the identical dimension because it shows the opposite views round it.
Now what?
I’ve managed to discover possibly two thirds of the brand new API, however there’s nonetheless extra to discover – I’ll replace this text quickly.
What’s your favourite change in SwiftUI for iOS 16? What do you suppose SwiftUI continues to be lacking to actually work to your apps? Tweet me @twostraws and let me know!
Sponsor Hacking with Swift and reach the world’s largest Swift community!