loading...

Looking in to SwiftUI

josephschmitt profile image Joe Schmitt ・4 min read

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 the NavigationView, not on the view itself. Why? Because a NavigationView can contain many sub-views, and each sub-view could/should/will have it's own title. This means that the following simple NavigationView 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 of NavigationView stacks. But, you can get around this and use custom Buttons'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 as struct ContentView: View, while the body property does body: some View? What does some 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 a class 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, a number. Once you create the number 5, you can't change what the number 5 means. This is different to a class which is instantiated and its location in memory is referenced. Once you initialize a class, not only can you update its properties, but all other references to that same class 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.

Posted on by:

josephschmitt profile

Joe Schmitt

@josephschmitt

I build things with my mind. Staff Software Engineer at Compass. Formerly Made for Humans, Float, Vimeo, Fi.

Discussion

pic
Editor guide
 

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 from body. So if you have an if statement inside body, you cannot return, say, a VStack from one branch and a Text from the other. You need to wrap either the if statement in a Group or wrap all your returned views in an AnyView.