What is SwiftUI?

Now that we have spent some time writing iOS applications using storyboards and Interface Builder we are going to take a look at a second method for writing apps. This method is the SwiftUI framework, which was introduced by Apple in 2018.

In SwiftUI we no longer use a graphical design tool to layout all of views for our apps. Instead, SwiftUI does everything in code. SwiftUI uses a declarative coding style in which we write code that sets up the user interface. At first this may seem inferior to using Interface Builder, since code is generally harder to write than just using a graphical design tool. As we will see, doing everything in code actually works pretty well, since both the code to set up the interface elements and the code for things like action methods are all going to appear together in the same place.

Our first SwiftUI project

To get started using SwiftUI, start by making a new project for a new app. The one change you will need to make when setting up the app is to choose SwiftUI for the app's interface in place of Storyboard when you do the project setup.

When the project opens you will see the code for the app's view class:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Here are some things to note about this code:

  1. The app's view is represented by a struct, ContentView, that inherits from the SwiftUI View struct.
  2. SwiftUI views are expected to have a body property. This is a computed property: the code to compute the body for the view appears in the curly braces.
  3. The code for the body property is expected to construct and return a single view object. In this example that view object is a Text object, which implements a simple label that displays the "Hello, World!" message.
  4. Often when construct interface elements we will apply various modifiers to the object to change its appearance or behavior. In this example, we are using padding modifier to put additional padding around the text. This will be helpful later when we put more objects in the view.
  5. The code above also includes code for a second struct, the preview struct. This is used to manage the process of making a live preview of interfaces as we build them. With the preview struct present Xcode can construct and display a live preview of our user interface as we write the code for the view.
  6. The code for this example is complete. You can click the run button in Xcode right away to load and run the app in the emulator.

A second example

Example Project

For our second example I am going to construct a simple "Hello, World" application in SwiftUI that includes several elements in the user interface, including a button with an associated action.

Here is the complete code for that example.

import SwiftUI

struct ContentView: View {
    @State var greeting : String = "Hello"

    var body: some View {
        VStack {
            Button("Greet Me") {
                greeting = "Hello, World!"
            }.buttonStyle(.borderedProminent)
            Spacer()
            Text(greeting)
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

As you can see, the only thing that has changed in this example is the code for the body property in the view.

Here are some things to notice about this code:

  1. The body property should always return a single view object. If you want to have multiple objects in your view you should put those objects in a single container view that holds all the objects. In this example I am using a VStack, which is a vertical stack view that holds everything else.
  2. Inside the VStack we construct a sequence of interface elements. Here we have a button, a text label, and a couple of spacers.
  3. The button has some text that it displays and some code in curly braces that serves as the action method for the button.
  4. Both the button's action code and the text label interact with a property of the view, the greeting property. This property is annotated with the @State property modifier. If you attach this modifier to a property and subsequently change the value of the property, the view will get rebuilt.
  5. Instead of using some text for the text label that displays the greeting, we pass the greeting property to the label instead. This will guarantee that whenever the view gets rebuilt the label will always display the current value of that property.

Here again is part of the code for the button:

Button("Greet Me") {
    greeting = "Hello, World!"
}

The code in the curly braces is actually code for a closure. This closure is essentially an anonymous function that the system will execute when the user clicks on the button. The closure in this example is what is know as a trailing closure: the code above is actually equivalent to this code:

Button("Greet Me",{greeting = "Hello, World!"})

This code sets up a closure and passes it as the second parameter to an initializer for the button.