Getting Started With SwiftUI

Written by Reinder de Vries on September 11 2019 in App Development

Getting Started With SwiftUI

With SwiftUI, you can declare the User Interface (UI) and behavior of your iOS apps – with Swift code! It’s a completely new paradigm and it changes how we think about building User Interfaces for iOS apps.

In this article, you’ll learn how to use SwiftUI to build User Interfaces for iOS. We’ll focus on the following topics:

  • What’s SwiftUI, and how is it different than UIKit?
  • How can you work with SwiftUI and its views?
  • The fundamental component in SwiftUI: views
  • The beauty of declarative programming
  • The Builder pattern, and modifiers and chaining
  • Working with stacks, such as the VStack and HStack
  • Table view and navigation controller equivalents in SwiftUI
  • Working with List, NavigationView and NavigationLink
  • Composability, and how it affects building UIs with SwiftUI
  • Answers to Frequently Asked Questions about SwiftUI!

Ready? Let’s go.

  1. What Is SwiftUI?
  2. The View: A Fundamental UI Component
  3. Builders, Modifiers And Chaining
  4. Stack ‘Em Like It’s Hot!
  5. Working With Lists In SwiftUI
  6. Working With Navigation In SwiftUI
  7. Frequently Asked Questions
  8. Further Reading

Updated for: Xcode 11.

Until iOS 13 and Xcode 11 are released publicly, SwiftUI is beta software and can only be used with the Xcode 11 beta. You need to download Xcode 11 beta and install it on your Mac, if you want to work with SwiftUI right now.

What Is SwiftUI?

SwiftUI is a declarative framework that you can use to build User Interfaces (UIs) for iOS, macOS, tvOS, watchOS and iPadOS.

It’s a completely new take on building UIs with Swift code, and it’s entirely different from the view controllers, XIBs and Storyboards you’re used to. SwiftUI was first announced during Apple’s Worldwide Developer Conference in 2019, and it’s widely regarded as the most exciting thing that’s happened to Swift since Swift!

Let’s take a look at an example. The snippet below declares a simple view with a text label that reads “Hello world!”

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

The above code declares a View with a body that includes some text. You don’t see any functions or any control flow, just declarations of UI components in a tree-like structure.

This is shockingly different from what you may be used to with UIViewController, UILabel and viewDidLoad(). In fact, SwiftUI isn’t built on top of UIKit. It can replace building UIs with UIKit entirely! (Note: SwiftUI can also incorporate UIKit components seamlessly, and vice versa.)

SwiftUI Hello World

Before we dive in deeper, let’s discuss what “declarative” means. It’s an important concept! We can divide modern programming languages into two broad categories:

  • Imperative: With imperative programming, we’re directly telling the program (or app) what to do and how to do it. You’re coding: “Put this button here, then download that piece of data, make a decision with if, and finally assign that value to a text label.”
  • Declarative: With declarative programming, we’re merely telling the program (or app) what to do, but not how. We’re building the logic of a program, without describing its control flow. The actual implementation is up to the program or its frameworks.

The biggest difference between imperative and declarative programming, is that declarative programming doesn’t involve control flow. A declarative programming language merely describes what needs to happen, a desired outcome called state, and leaves the implementation up to another program or framework.

A good example of a declarative language is HTML and CSS. They’re used to build web pages by providing so-called markup and styling rules. You don’t use HTML and CSS to draw pixels on a screen, or to tell a browser how to render your web page exactly. Instead, you tell it what components to put on screen, and what they should look like. You leave the rest up to the web browser.

SwiftUI uses a declarative programming approach. Instead of initializing UI components programmatically, and adding them to a view, you’re merely describing what the view looks like. When you’re building a UI with SwiftUI, it’s helpful to think not about how you want to accomplish a result, but instead about what the UI looks like in it’s new state.

SwiftUI is closely related to modern development tools and paradigms, such as React, React Native, Flutter, and reactive programming. Platforms like React Native and Flutter have always been one step ahead when it comes to rapid application development (RAD), but it appears SwiftUI puts app development for iOS and co. right back at the forefront. SwiftUI’s launch was certainly not uninspired!

You can use Live Previews with SwiftUI in Xcode 11. It’s an awesome feature, because it’ll let you reload your app’s UI right in Xcode without building the app on Simulator (or a device) first! This feature is similar to Hot Reload for Flutter, and Live Reload for React Native. One caveat, is that it only works on macOS Catalina. If you’re running the Xcode 11 beta on Mojave, you can’t use Live Previews. You can continue with this tutorial though – no Live Previews needed!

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 13 apps with Swift 5 and Xcode 11.

The View: A Fundamental UI Component

Building UIs with SwiftUI revolves around a fundamental UI component: the View. It’s a Swift protocol, and you can see how it’s used in that snippet we looked at earlier.

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

The struct ContentView conforms to the View protocol, and so does its body property. Every View must to declare a body property of type View. The body property is computed, read-only, and must return only one instance of View. This hints at a tree-like view hierarchy – a fundamental concept of SwiftUI – because you can wrap views in views in views, creating a complex hierarchy of views.

If you’re familiar with Model-View-Controller and view controllers, you know that the view represents the UI of the app. A view controller includes the business logic, i.e., what happens when, that connects the UI and data models with each other. Consider a to-do list app, for example. If you tap the checkbox for a to-do item, the view propagates that interaction to the controller, which will prompt the model to save its isCompleted property.

With SwiftUI, the controller part of Model-View-Controller is taken out of the equation (almost) entirely! A new concept, called bindings, is used to connect views to models, and vice versa. It’s reminiscent of Observer-Observable, NotificationCenter and “MVVM”, but it propagates data changes much more elegantly.

This emphasis on views and models is a superpower in SwiftUI. SwiftUI underscores that an app doesn’t do much else than display UIs and manipulate data. In a way, modern iOS apps are nothing more than so-called CRUD applications, which stands for “Create-Read-Update-Delete”. Every action in an app can be represented as any of those 4 types – more or less.

Let’s get back to that view example once more:

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

In the above code, the some keyword denotes an opaque type, which is part of the generics feature group of Swift 5.1. An opaque type can be used with protocols, such as in var body: some View. It means that the value of body conforms to protocol View, but we’re unaware of its concrete type. The concrete type is abstracted away; it is “opaque”. It’s like saying: “Look, this value is a View, but we’re not telling you exactly what type of View it is.” This has a few advantages, such as simplifying the public interface of a type. As a developer, you can deal with the value as a View, without knowing its specific, concrete type.

What’s also interesting about the implementation of the body property in the above code, is the use of return. But wait, the function doesn’t return anything!? That’s right – the code for body returns the value of type Text implicitly, without making use of return.

Here’s an example, that uses return explicitly:

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

In Swift 5.1, single-line functions, such as the one above, may omit the return type. This makes for some pretty concise, readable code! And it’s a feature that SwiftUI uses extensively.

Enough about views, let’s move on to building some UIs!

In my opinion, SwiftUI places iOS’s default architecture in the Model-View-Whatever category. Instead of using Model-View-Controller, in which the controller has an important role, Model-View-Whatever merely defines the use of User Interfaces (views) and data representations (models). What(ever) you put between models and views is up to you! On iOS, the business logic of an app, beyond using bindings, is likely to be assigned to the manager, helper, builder, coordinator, delegate, etc. roles of architectural design patterns. The new Combine framework will also play a role here.

Builders, Modifiers And Chaining

Let’s riff on the previous example some more, to discover more of the features of SwiftUI. We’ve discussed views and the view hierarchy, but what if you want to change the style of a view?

In SwiftUI, you can change views with functions called modifiers, like this:

struct ContentView : View {
    var body: some View {
        Text("Hello World")
            .font(.title)
            .color(.blue)
    }
}

In the above example, we’ve chained the Text value to a call to function font(_:), and chained that to a call to function color(_:). This will first change the font of the text to iOS’s default title font, and then sets its color to blue. These functions are called modifiers in SwiftUI lingo.

This approach is called the Builder pattern. You start with a basic view, and by tacking on modifiers you change the attributes of that view. What’s interesting is that every modifier function call returns a new View instance. That’s why you can “chain” and combine multiple modifiers to get to a specific result.

But wait, is the code for body still a single-line expression? Yes it is! We might as well write the entire chain on one line, like this:

Text("Hello World").font(.title).color(.blue)

Your code is easier to read if you stack the modifiers vertically, indented with one tab, but the actual implementation is still considered a single-line expression. No return needed!

Now, there’s something else going on with these builders. Those .title and .blue constants, where do they come from? Why don’t we just choose the text’s RGB color and pick the default system font?

SwiftUI uses sensible defaults that take into account many aspects of iOS, such as a user’s preferences. This means Dark Mode is supported out-of-the-box if you use SwiftUI.

And there’s more! Accessibility features, such as a user’s preference for larger fonts, are automatically taken into account. Likewise, the .blue color isn’t the same as the RGB color #0000FF – a hard blue. Instead, it’s iOS 13’s take on a shade of blue, which makes it easier for your app to fit in with iOS’s styling.

Stack ‘Em Like It’s Hot!

Now that we’ve discussed views and builders, let’s take a look at building a more complex UI. What if you want to compose a UI that consists of multiple views? That’s where stacks, lists and groups come in.

SwiftUI’s views are composable. Just like Mozart and Beethoven use composition to turn many distinct musical notes into one coherent piece of music, you use many individual views to compose your app’s complete User Interface. The View building blocks can be combined and nested in many, many ways.

As we’ve discussed before, the body property of a View instance needs to return exactly one view. If you want to compose a view that consists of an image and some text, how are you going to do that if a view can only have one subview? With stacks!

SwiftUI provides 3 distinct stacks:

  • HStack, which arranges its children (i.e., subviews) in a horizontal line, next to each other
  • VStack, which arranges its children in a vertical line, i.e. above and below each other
  • ZStack, which overlays its children, i.e. places them on top of each other along the z depth axis

Here’s an example of the VStack in action:

struct ContentView : View {
    var body: some View {
        VStack {
            Text("A beautiful landscape")
                .font(.title)
            Image("landscape")
        }
    }
}

In the above code, we’re composing a VStack view that consists of two children: a Text view and an Image view. The VStack itself is a View instance, of course, that accepts an unnamed parameter content. In short, this content parameter is a closure that uses the ViewBuilder type to construct views. Differently said, we can put subviews in the VStack and have them show up vertically in the UI.

SwiftUI VStack Example

You can also combine a horizontal and vertical stack, like this:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("John Appleseed")
                .font(.title)
            HStack {
                Text("iOS developer at Acme Inc.")
                    .font(.subheadline)
                Spacer()
                Text("+1-202-5385-1234")
                    .font(.subheadline)
            }
        }
        .padding()
    }
}

What’s going on in this block of code?

  • The top level element is a VStack with its alignment set to .leading, which means that its subviews are aligned to the left of the view. On the last line, the VStack is also given some space around its edges with .padding().
  • The vertical stack consists of 2 child views: some text and a horizontal stack. The text’s font is set to .title.
  • The horizontal stack has 3 children: text, spacer, text. The spacer is interesting, because it’ll press both text items to the left and the right of the view.

View hierarchy diagram

The previous snippet is inspired by one of SwiftUI’s official tutorials, called Creating and Combining Views. You’re building a Landmarks app in that tuturial, and I recommend you check it out!

Working With Lists In SwiftUI

The big question is of course: Can you build lists with SwiftUI, and how? UIKit provides the ubiquitous table view controller with its scrollable list of cells, rows and sections. Almost every app uses table views! Is there a similar component for SwiftUI?

So far, it looks like the List view is the equivalent of a table view. You can show a list of rows in this component. Let’s take a look at how that works! In the next few steps, we’ll build a simple Contacts list app.

SwiftUI List Example

First, we’re going to define a model called Contact. Here it is:

struct Contact
{
    var name:String
    var jobTitle:String
    var phone:String
}

Simple, right? It’s an ordinary struct with 3 properties of type String. We’ll now fill up an array contacts with some instances of this new struct. This array will serve as the data source for the list view.

let contacts = [
    Contact(name: "Marvin", jobTitle: "Paranoid Android", phone: "+1-200-8261-0817"),
    Contact(name: "Arthur Dent", jobTitle: "BBC Radio employee", phone: "+1-200-1234-5678"),
    Contact(name: "Zaphod Beeblebrox", jobTitle: "President of Universe", phone: "+1-200-7162-2715"),
    Contact(name: "Ford Prefect", jobTitle: "Alien journalist", phone: "+1-200-8162-7651"),
    Contact(name: "Trillian Astra", jobTitle: "Mathematician", phone: "+1-200-9876-5432"),
]

If you’re following along in Xcode, make sure to add the Contact struct to its own Swift file. Add the contacts array outside the struct, so you can use it globally in your project.

The next step is creating the ContactRow view. Xcode 11 has a neat template that you can use to build views with SwiftUI. Here’s how:

  1. Choose the File -> New -> File... menu
  2. Find the SwiftUI View template below User Interface and click Next
  3. Give the Swift file a sensible name, such as ContactRow.swift and click Create

This will automatically create the boilerplate code that’s needed to get started with a SwiftUI view. Great! Here’s the view that we’re going to use:

struct ContactRow : View {
    var contact: Contact

    var body: some View {
        VStack(alignment: .leading) {
            Text(contact.name).font(.title)
            HStack {
                Text(contact.jobTitle).font(.subheadline)
                Spacer()
                Text(contact.phone).font(.subheadline)
            }
        }
    }
}

A few things stand out here:

  • The view is basically the same as the view from the previous chapter, with 3 text elements in a vertical and horizontal stack.
  • The ContactRow view has a contact property of type Contact. That’s the data model we defined earlier. In this app, it’ll represent the data that’s going into the view.
  • Instead of hard-coded strings, we’re using the properties of contact to fill the views with the text. The name property is used for the top text, and the jobTitle and phone properties are used for the left and right text at the bottom of the row.

Take a step back now, and look at your code from a wider perspective. What’s going on here? First, we created the data model. Then, we created the row that goes into the list. The row has a property contact that accommodates one instace of the Contact model. It uses the properties of the model to populate the row.

If you’re familiar with table views, see if you can match what you know about table views with what you know know about List. Where do the data source, the cells, etc., fit in?

Finally, we can declare the main view of the app. It includes that List element and some logic to deal with assigning model instances to the list’s rows. Here we go:

struct ContentView : View {
    var body: some View {
        List(contacts, id: \.name) { contact in
            ContactRow(contact: contact)
        }
    }
}

See the List element in the above snippet? By default, this view accepts subviews, similar to a VStack. Like this:

List {
    ContactRow(contact: contacts[0])
    ContactRow(contact: contacts[1])
    ContactRow(contact: contacts[2])
    ...
}

The above example hard-codes ContactRow views in the List element, by using the items in the global contacts array. However, this approach doesn’t load the rows dynamically!

If we want to load rows dynamically, we’ll have to provide those to the List element. The default initializer of the List element accepts a @ViewBuilder closure, which constructs view elements from closures. The initializer we’ll use takes an array, a keypath, and a closure that provides the views for the list.

Like this:

List(contacts, id: \.name) { contact in
    ContactRow(contact: contact)
}

The above code looks simple, but there’s a lot going on. Here’s what:

  • The initializer List(_:id:rowContent:) is used to populate the list. It’ll accept an array, a keypath, and a closure. The first argument is simple, that’s the contacts array we want to use. The second parameter id contains a keypath, which is used to uniquely identify rows in the list. We’ll use the name property of a Contact item. The last argument is a closure, which gets an item from the contacts array as an argument, and needs to return a value of RowContent to its caller. In short, the List takes an array and a keypath, and needs to return a view for every item in the array.
  • The closure is invoked to provide views for every item in the collection (from contacts). This essentially passes us one contact item, for which we’ll have to return a View instance.
  • Inside the closure, we’re initializing an instance of type ContactRow, using the contact value. The ContactRow(contact:) initializer is automatically synthesized for us.

Differently said, for each item in the contacts array, a ContactRow view is initialized, and added to the List view. What you end up with, is a list of rows with contact information. Neat!

SwiftUI together with Realm Database is an exceptionally powerful combo. With just a few lines of code you can build a model-view app, backed by an offline-first database.

Working With Navigation In SwiftUI

The next step is adding navigation to our app. This is a common approach for iOS apps, in which you support left-to-right navigation with a UINavigationController. Can you achieve similar results with SwiftUI?

Yes! And as it turns out, adding navigation to your app is incredibly simple. Here’s how we’re adding a NavigationView element to the list UI we built earlier:

struct ContentView : View {
    var body: some View {
        NavigationView {
            List(contacts, id: \.name) { contact in
                ContactRow(contact: contact)
            }
            .navigationBarTitle(Text("Contacts"))
        }
    }
}

In the above code, we’ve wrapped the List view in a NavigationView element. This is essentially all that’s needed start working with navigation!

The .navigationBarTitle(_:) modifier is used to display a text view in the navigation bar, as a title. It’s important to note here that the modifier is inside the NavigationView element. Why? Because it’s the navigation title of the List element – the navigation view merely wraps that list.

Let’s take a look at how we can actually navigate to a next view. Here’s the SwiftUI code we’ll work with:

NavigationView {
    List(contacts, id: \.name) { contact in
        NavigationLink(destination: ContactDetail()) {
            ContactRow(contact: contact)
        }
    }
    .navigationBarTitle(Text("Contacts"))
}

This is what happens in the above code:

  • The NavigationView wraps the List view, but unlike before, the items in the list are now NavigationLink elements. This is what’s used to facilitate side-by-side navigation.
  • The NavigationLink view has two parameters. A destination, which is another View element, and an unnamed label parameter. That’s the ContactRow view. It’s easiest to see this as the navigation destination and the navigation button itself. You tap the button to go to the destination, i.e. you tap the ContactRow to go to the ContactDetail view.

SwiftUI NavigationView Example

The code we’ve written so far also exposes another problem. You can see how these hierarchies of nested views can get huge! If you wrap views in views in views – i.e., a complete UI – you can easily get lost. When you combine those nested views with 5-10 modifiers for every view, it gets cluttered quickly. And that’s a PITA to debug!

How can you deal with this issue effectively? First and foremost: SwiftUI is a new technique, so it’s a great idea to make lots of mistakes. At this point, no one knows how SwiftUI will behave in production apps. Many developers and engineers will give you recommendations, but it’s a great idea to figure out what works for you. Make the technology your own, so to speak.

The official SwiftUI tutorials make use of subclassing and composition. Take for example the ContactRow view that we’ve built ourselves. If you feel that your view hierarchy gets nested too deeply, you can decide to abstract away some prototypical views into their own structs. You can then replace the code with an instance of that new struct.

Likewise, you can abstract away view modifiers that you’re using for common views. If your text views always use padding, you might as well create a view called PaddedText, that returns a text view with Text().padding().

Frequently Asked Questions

Let’s take a look at a few Frequently Asked Questions, or, better said, Commonly Held Confusions.

When can I use SwiftUI in my apps?
SwiftUI is part of iOS 13, and can’t be used on previous versions of iOS. You’ll need Xcode 11 to work with SwiftUI, too. SwiftUI is currently beta software, and it’s expected to ship with the upcoming releases of Xcode, iOS and macOS. This places the earliest date for SwiftUI in production somewhere in September 2019. But… you can already get started with coding SwiftUI today!

What consequences does “iOS 13 and later” have for SwiftUI?
In short, it means that you can’t use SwiftUI easily if your app needs to support iOS versions prior to iOS 13. As a benchmark, we know that iOS 12 was adopted on about 85% of Apple devices. Assuming the same thing happens with iOS 13, it’s up to you if your app can afford to give 15% of iOS users the cold shoulder. It’s likely that SwiftUI will see a moderately slow adoption phase, just like Swift in 2014 and 2015. Whatever you do, don’t shy away from experimenting with SwiftUI though!

What happened to UIKit? Is everythign I know about view controllers obsolete now?
That’s an interesting question. SwiftUI is not built on top of UIKit. This means that SwiftUI has the potential to replace everything you know about UILabel, UIButton, UIViewController, table views, collection views, Auto Layout, and much, much more. Yikes! The reality of SwiftUI is probably not going to be that extreme, though. Think about Swift vs. Objective-C: many apps these days still use Objective-C. In the coming years, many apps will keep using UIKit – because it works fine. Until then, it’s a smart idea to learn more about SwiftUI and adopt it as you see fit. It’s yet another argument to keep learning more about iOS development!

Can I use SwiftUI together with UIKit?
Yes! SwiftUI and UIKit are interoperable, which will greatly help SwiftUI’s adoption in existing iOS apps. Interfacing with UIKit from SwiftUI revolves around the UIHostingController, UIViewRepresentable and UIViewControllerRepresentable protocols. In short, you can wrap views and view controllers from UIKit in SwiftUI views, and vice versa.

Can SwiftUI handle complex views?
Great question. So far, we’ve only seen simple apps that use SwiftUI. The SwiftUI tutorials have Landmarks, which is a simple app that has 2-3 UIs, and that’s it. What about complex UIs, can SwiftUI deal with that? Well, the most pragmatic answer here is: “No one knows, until someone has the guts to find out!” The best thing you can do here is to try out if your complex UI can be built with SwiftUI, and to share your insights with other developers. That said, my gut feeling tells me we’re going to see a whole lot more of SwiftUI in the coming years. SwiftUI is awesome today, but it’s going to be way more awesome if SwiftUI takes the same path as Swift 1.0 to 5.1.

Is SwiftUI merely a prototyping tool?
I personally don’t think so, but SwiftUI does put iOS and co. back on the map as a Rapid App Development (RAD) tool. A fair share of app designers with coding skills used Storyboards to mock up functional app prototypes, and that’s definitely something you can do more easily with SwiftUI. That said, I don’t think SwiftUI is merely a prototyping tool.

How can I install the beta of macOS Catalina?
Yes – great idea, but be careful! You can install the beta of macOS Catalina on your Mac, side-by-side with macOS Mojave. It’s not recommended to upgrade your everyday Mac to Catalina, because it’s beta software. You can also install Mojave in a virtual machine, and upgrade it to Catalina. Google is your friend!

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 13 apps with Swift 5 and Xcode 11.

Further Reading

Pfew! SwiftUI is pretty awesome, right? This article barely touches the surface of what’s possible with SwiftUI. In the weeks, months and years to come, iOS developers will make SwiftUI their own and push the limits of what’s possible with this new technology. It’s up to you what you’re going to build, try out, and experiment with!

It’s recommended you check out some of the official documentation around SwiftUI. These docs are exceptionally readable, and in particular, the SwiftUI Tutorials are worth checking out.

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.