One of the most exciting things to come out of the last couple WWDC's is Swift UI. If you're unfamiliar, it's a UI framework that makes writing UI code much lighter and easier by using a lot of concepts that have been popularized by things such as React.
Swift UI is clearly the future of Apple's platforms, and since I'm learning this stuff from scratch anyway, I may as well embrace the future. So my new app will be written in as close to 100% Swift UI as I can manage, and require iOS 14.
But, now I've just added something else I have to learn. Luckily, it seems like a lot of Swift UI's syntax is built upon the basics of Swift, so learning both together shouldn't be too bad.
The first thing I did was to try and figure out what all Swift UI is capable of. Luckily Apple has some great tutorials over on their developer site to help get started. These have been an invaluable resource and I'll be sure to reference them as I get going.
The other place I've been spending my time to try to get my head around what SwiftUI can do is on Paul Hudson's YouTube channel. These have been especially great. I'm not sure what it is about his explanation style, but his videos have led to more "aha!" moments when it comes to understanding the major concepts than most.
I started out with this video on NavigationView
, which is one of the simplest and most basic concepts in a UI stack. A few of the major things I learned here:
- A
navigationBarTitle()
is called on a view inside theNavigationView
, not on the view itself. Why? Because aNavigationView
can contain many sub-views, and each sub-view could/should/will have it's own title. This means that the following simpleNavigationView
stack looks a bit weird:
struct ContentView: View {
var body: some View {
NavigationView {
Text('Hello world')
.navigationBarTitle('Navigation')
}
}
}
Looking at this code you would think you'd apply .navigationBarTitle
on the NavigationView
itself. But this is simply because SwiftUI is handling something really clever: everything is a View. Views are the most basic foundation of SwiftUI, and so when you create a Text()
it's actually not just creating the text itself, but also the entire wrapping view the text lives in. So as your NavigationView
hierarchy gets more complex, this makes more sense:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text('Hello world')
}.navigationBarTitle('First View')
VStack {
Text('Hello world, again')
}.navigationBarTitle('Second View')
}
}
}
Now it's easier to see what's going on, and why we'd want to apply the title to a sub-view and not the NavigationView
itself.
-
NavigationLink
seems to be the way SwiftUI wants you to dig into and out ofNavigationView
stacks. But, you can get around this and use customButtons
's if you bind them to certain variables
I left the video with a bunch of questions, though.
- Why does the wrapping view struct implement the
View
protocol asstruct ContentView: View
, while thebody
property doesbody: some View
? What doessome View
mean? - In the video he applied some decorators to his variables like
@State
and@ObservableObject
which I have no idea what they do. He also sometimes passed a variable as it was named, and sometimes with a$
in front of it.
That's fine, I've only just started looking at SwiftUI code, let alone understanding it.
And it just so happens that there's a video on the same channel that explains this: What's the difference between @State, @ObjectBinding, and @EnvironmentObject?
This video was awesome, and it explained so many wonderful things to me
-
SwiftUI's views are all structs, not classes.
Why is that important? Because a
struct
is passed by value whereas aclass
is passed by reference. Additionally, once you've created a struct, it's immutable and can't change. This makes sense if you think about it in terms of another type of struct, anumber
. Once you create the number5
, you can't change what the number5
means. This is different to aclass
which is instantiated and its location in memory is referenced. Once you initialize aclass
, not only can you update its properties, but all other references to that sameclass
instance will also instantly get those changes.How does this relate to structs? Well, if our view structs were permanently immutable, then we'd never be able to update them with new data. To solve this issue, SwiftUI provides the
@State
and@ObjectBinding
decorators. What these do is they tell SwiftUI that these specific variables might change, and if they do, then the View should re-render.What's the difference between
@State
and@ObjectBinding
? Simple: a@State
has one-way binding, meaning data only flows one direction, while@ObjectBinding
has two-way binding, meaning it can change in both directions. This also answers the question I had earlier about why some variables had a
$
in front of them. When they do, it means that they're bound, and therefore should be monitored for changes. Without it, the compiler will just fix them to their value during compile time.
These videos have been hugely helpful so far, and Paul is a joy to watch. I'll be checking out more of his SwiftUI videos as I learn. But I think at this point I understand enough of the code being written in these videos that I can start prototyping some parts of my app. The best way to learn is by doing.
Top comments (1)
The
some View
is a general Swift feature called opaque types. And of course Paul Hudson has a great explanation of it.The important thing to keep in mind there is that you have to always return the same actual type of
View
frombody
. So if you have anif
statement insidebody
, you cannot return, say, aVStack
from one branch and aText
from the other. You need to wrap either the if statement in a Group or wrap all your returned views in an AnyView.