UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Get started with SwiftUI

Hands-on code to help you get moving fast.

Paul Hudson       @twostraws

SwiftUI is Apple's incredible new user interface framework for building apps for iOS, macOS, tvOS, and even watchOS, and brings with it a new, declarative way of building apps that makes it faster and safer to build software.

If you're already using UIKit there's a bit of a speed bump at first because we’re all so programmed to think in terms of UIKit’s flow, but once you’re past that – once you start thinking in the SwiftUI mindset – then everything becomes much clearer.

At the launch of SwiftUI at WWDC19, Apple described it as being four things:

  • Declarative, meaning that we say what we want rather than how we get there.
  • Automatic, meaning that it takes care of many things we had to do by hand previously.
  • Compositional, meaning that we build small things and combine them together into larger things.
  • Consistent, meaning that we don’t see strange fault lines between Swift and Objective-C like we did previously, or very old APIs mixed up with very new ones.

Having now written tens of thousands of lines of code with SwiftUI I can tell you they missed one important point off: concise. Your SwiftUI code will be maybe 10-20% of what your UIKit code was – almost all of it disappears because we no longer repeat ourselves, no longer need to handle so many lifecycle methods, and more.

Let’s dig in to how SwiftUI works…

 

  • Update: I've released a massive, free guide to SwiftUI here: SwiftUI by Example – it contains a huge number of code samples and solutions for common SwiftUI problems, plus a long video showing you how to build your first complete project.
  • You can also now follow my free 100 Days of SwiftUI curriculum to learn SwiftUI in a hands-on way.

 

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

What is a View?

In SwiftUI, View is more or less what we had with UIView, with two major differences:

  1. It’s a protocol rather than a class, so we don’t get stuck in inheritance problems.
  2. Our view must always return one view to render. That view might internally contain other views, but it’s still just one parent view going back.

Apple’s default sample code gives us this view:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

You’ll notice a few things there:

  1. It returns some View, which is an opaque type – watch my video on opaque return types in Swift 5.1 if you haven’t seen these before.
  2. It returns exactly one thing: a Text view with the text “Hello World” – that’s equivalent to a UILabel in UIKit terms.
  3. It doesn’t use the return keyword – that’s another Swift 5.1 change, so you might want to watch my video for that too.

Creating layout stacks

Remember, our views must return precisely one thing inside them – not 0, 2, or 200. So, if you want to show multiple labels you need to place them inside a container such as a HStack, a VStack, or a ZStack – effectively horizontal stack views, vertical stack views, or a stack view that positions one thing on top of another.

So, if you wanted to place three labels above each other you’d write this:

var body: some View {
    VStack {
        Text("Hello World")
        Text("Hello World")
        Text("Hello World")
    }
}

By default these stacks take up only the space they need, so you’ll see those three text views bunched up in the center of your screen. If you want to force them apart, you can insert a flexible space control using Spacer, like this:

var body: some View {
    VStack {
        Text("Hello World")
        Text("Hello World")
        Spacer()
        Text("Hello World")
    }
}

Now you’ll see the first two text views at the top of your screen, then a large gap, then the final text view at the bottom of your screen. For a more subtle effect, use Divider rather than Spacer – this creates a horizontal rule between elements, giving a little visual space between them without pushing them far apart.

Creating lists of content

Everyone who has used UIKit knows the monotony of creating table views: we need to register cell prototypes, tell UIKit how many items we have, dequeue and configure cells, and more – it’s tedious, and it’s repetitive.

And it’s all gone with SwiftUI.

Tables in SwiftUI use a new List view, which can show static or dynamic content. For example, we might have a struct that defines users, like this:

struct User {
    var firstName: String
    var lastName: String
}

If we wanted to show that inside rows in a list, we’d start by defining what one row looks like. We know it needs to work with a User, so we would add that as a property. And we know it needs to show their names, so we’d make our view body return a text view.

In SwiftUI, it’s this:

struct UserRow: View {
    var user: User

    var body: some View {
        Text("\(user.firstName) \(user.lastName)")
    }
}

Finally, we can update our ContentView struct so that it creates a couple of users then puts them in a list:

struct ContentView: View {
    var body: some View {
        let user1 = User(firstName: "Piper", lastName: "Chapman")
        let user2 = User(firstName: "Gloria", lastName: "Mendoza")

        return List {
            UserRow(user: user1)
            UserRow(user: user2)
        }
    }
}

While that works fine for simple lists, for anything more advanced you’re going to want to use dynamic content – to pass in an array of data and have SwiftUI figure out how many rows it needs, create them all, and show them all.

To make that happen, we must make User conform to the Identifiable protocol so that SwiftUI knows which person is which uniquely. Although it could check for this by querying every single property, that’s slow and likely to lead to false positives. So, instead we need to give our structs a unique id value that we know won’t be duplicated by other items in the same list.

So, we could update User to have a id property like this:

struct User: Identifiable {
    var id: Int
    var firstName: String
    var lastName: String
}

You can use anything you like for your identifier – strings, UUIDs, or whatever, are all fine.

Now that we have that we can create an array of our users – which could just as easily come from some Codable input you’ve downloaded from the internet – and pass that into the list as we create it. We then pass a closure to run that configures individual rows in the list, like this:

struct ContentView: View {
    var body: some View {
        // create some example data
        let user1 = User(id: 1, firstName: "Piper", lastName: "Chapman")
        let user2 = User(id: 2, firstName: "Gloria", lastName: "Mendoza")
        let users = [user1, user2]

        // show that data
        return List(users) { user in
            UserRow(user: user)
        }
    }
}

Just to be clear: about half that code is just me creating some example data – the actual SwiftUI part to create and show many list rows is only three lines of code, and that’s only if you include the closing brace on a line by itself.

In fact, if you’re just showing rows like that, you can collapse it down even further:

return List(users, rowContent: UserRow.init)

Boom.

Seriously, SwiftUI will demolish so much of your code.

Before we’re done with lists, there’s one more thing: we can change UserRow freely without worrying about how it’s used elsewhere. Remember, SwiftUI is designed for composability: we pass a user into UserRow and let it figure out how it’s displayed, rather than always having our main views control everything like we did with UIKit.

So, if we wanted UserRow to show two labels stack vertically, with one large and one small, and with both labels aligned to their leading edge, we’d write this:

struct UserRow: View {
    var user: User

    var body: some View {
        VStack(alignment: .leading) {
            Text(user.firstName)
                .font(.largeTitle)
            Text(user.lastName)
        }
    }
}

Beautiful.

Showing a detail screen

Showing just one screen isn’t very interesting, so let’s create another.

First, we’ll say this screen should show just the last name of the user that was selected – nice and big, and in a red color:

struct DetailView: View {
    var selectedUser: User

    var body: some View {
        Text(selectedUser.lastName)
            .font(.largeTitle)
            .foregroundColor(.red)
    }
}

Next we want to embed the list in ContentView inside a navigation view, which is equivalent to a navigation controller in UIKit:

struct ContentView: View {
    var body: some View {
        let user1 = User(id: 1, firstName: "Piper", lastName: "Chapman Yay")
        let user2 = User(id: 2, firstName: "Gloria", lastName: "Mendoza")
        let users = [user1, user2]

        return NavigationView {
            List(users, rowContent: UserRow.init)
        }
    }
}

By default navigation bars don’t have a title, so you should attach a title to your list like this:

List(users, rowContent: UserRow.init)
    .navigationBarTitle("Users")

Of course, what we really want is for tapping items in our cells to show our detail screen, passing in whichever user was tapped.

To make that happen we need to wrap our user rows inside a NavigationLink. This gets created with a handful of parameters, but here we’re going to use only two: where to send the user when the item is tapped, and a trailing closure specifying what to put inside the navigation item. In our case, that’s our user row with whatever user is being shown.

So, replace the navigation view with this:

return NavigationView {
    List(users) { user in
        NavigationLink(destination: DetailView(selectedUser: user)) {
            UserRow(user: user)
        }
    }.navigationBarTitle("Users")
}

Now two things have happened. First, if you click the small play button at the bottom-right corner of the preview area, you’ll find you can now tap on list rows to show the detail view we made. Second, you might also have noticed that our list rows have disclosure indicators – that’s what Apple meant about SwiftUI being automatic.

Where next?

Once you've spent some time with SwiftUI it's fair to say that going back to UIKit feels like going back to Objective-C – methods like viewDidLoad() that we took for granted now seem just a bit alien, and you start to resent writing boilerplate code that effectively disappears in SwiftUI.

If you'd like to learn much more about SwiftUI, I published a massive, free SwiftUI tutorial called SwiftUI By Example, which walks you through a complete project from scratch then dives into well over 100 solutions for common problems you'll face – how do you create text fields? How do you work with Core Data? How do you animate changes? All that and more are covered, and it's online for free.

I’d love to hear your views on SwiftUI – send me a tweet at @twostraws and let me know what you think!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.9/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.