TANASCHITA.COM
Articles about Swift and iOS Development by Natascha Fadeeva

Quick guide on SwiftUI essentials

Get started with SwiftUI by reading this quick guide. It introduces the basic SwiftUI concepts and terms for view creation and state management.

Published 17. October 2020 - 10 min read

It was really exciting to hear about Apples new framework called SwiftUI at WWDC 2019. This post is a quick guide to get started with SwiftUI.

At first, we will take a look at how views in SwiftUI are created, layouted and presented. Then, we will examine how state is being managed with SwiftUI. And finally, we will take a look at how SwiftUI and UIKit play together.

This world is so exciting, I want to know it all!

Views in SwiftUI

With UIKit we had different ways to build user interfaces. Many discussions were held whether to build UI programmatically or by using the Interface Builder. With SwiftUI those problems are gone. We finally have a declarative way to write UI in code and to preview it side-by-side with the code.

Basic SwiftUI view

A SwiftUI view is just a usual .swift file with two structures.

The first structure, that is usually a struct, is the view itself. All views in SwiftUI conform to the View protocol which requires a computed property called body that contains the layout for the view. The second structure declares a preview for that view.

A basic SwiftUI view example.

In the example, the view returned by the body property is a VStack, a vertical stack which contains two other views, an Image and a Text. On the right side, Xcode generates a preview, so we can directly check our written code.

Aligning views

When implementing SwiftUI views, we do not have to set up constraints like with UIKit views. Instead, SwiftUI figures out the intrinsic size of each view and positions it by default in the center of it's parent view without resizing it.

The core concept for aligning views in SwiftUI is by using stacks, the VStack for vertical or the HStack for horizontal alignment.

Using HStack and VStack to align views.

As shown in the example, we can nest different stacks to achieve the desired result, the concept is really intuitive.

Additionally, there are different modifiers we can use on each view to align and position them. For example, the frame(width:height:alignment:) wraps the view into a container of the given size and aligns the view within this container.

Using the frame modifier to align a view.

Other modifiers like position(x:y:) and offset(x:y:) can also be used to position the child in its parent’s coordinate space.

Furthermore, we can use Spacer to align views. It is a flexible space that expands as long as there is space.

Using Spacer to align views.

As we can see, the Spacer helped us to move the text to the right side of the parent view.

View modifiers

The view's appearance can be modified by using so called modifiers. Most modifiers provided by SwiftUI are self-explanatory. We have already seen some of them in action in the examples above like font(), background(), padding() etc.

It is also possible to create our own modifiers, for example if we want to reuse styles.

Example of a custom view modifier.

In the example above, we define a custom modifier named PrimaryButtonModifier that we can now use to style our buttons. As shown in the preview, to apply the custom modifier to a view, we can use the .modifier() modifier.

Lists

Creating a list of views is a common task when building an application. It is really simple achieved in SwiftUI with the List view.

Example of a list view.

We just need to pass our data and an id into the List view and than configure the rows with this data.

In our simple example, we can use the hashValue property of String as the id. In a real world application, we are more likely to work with structs or classes for our data definitions. In this case, we can make our objects conform to the Identifiable protocol, then SwiftUI will automatically use it's id property.

Navigating between views

Navigation between views is another common task that is also easy to achieve with SwiftUI.

To add navigation that is similar to a UINavigationController in UIKit, we can embed the view in a NavigationView to add a navigation bar and then use a NavigationLink to set up a transtition to a destination view.

Example of a navigation view.

In the example, we wrap a list into a NavigationView and the rows of the list into a NavigationLink. So when tapping a row, the user will be directed to the FruitDetailView. Of course, instead of a list we could use any other views to setup a navigation behaviour like this by wrapping them in the same way.

Handling state

A big change in SwiftUI compared to UIKit is how state is being handled. State represents the data associated with a view. With SwiftUI, when state properties change, the views are updated automatically.

To be able to achieve those automatic updates, Apple provides different property wrappers like @State, @Binding, @StateObject and more. Let’s take a closer look at those property wrappers and how they relate to each other.

State and Binding

The State and Binding property wrappers are used inside of View objects and allow the view to respond to any changes made to the state properties.

Let's take a look at the following view that shows a text and a text field. We want the Text view to always show the current input from the TextField view.

struct ExampleView: View {

    @State private var username: String = "" // 1

    var body: some View {
        VStack {
            Text(username) // 2
            TextField("username", text: $username) // 3
        }
        
    }
}

To achieve this, we

  1. Create a State-wrapped property called username.
  2. Initialize our Text view with this property. From now on, every time username changes, the Text view will update automatically.
  3. Bind the username property to the text field. We recognize the binding by the fact that the property is passed in with an $ prefix. From now on, every time the text field's input changes, our username property will also be updated.

The binding in the last step is possible, because the text property of TextField has the type Binding<String>. That means, it is wrapped with Binding internally.

The difference between Binding and State is, that State is used for the view's private state, whereas Binding creates a two-way connection between a view and a state property defined outside of that view.

StateObject and ObservedObject

While State and Binding are used for value types, StateObject and ObservedObject can be used for reference types. The reference type needs to conform to ObservableObject.

Let's look at an example.

class User: ObservableObject { // 1
    @Published var firstName: String = "" // 2
    @Published var lastName: String = ""
}

struct ExampleView: View {
    
    @ObservedObject var user: User // 3
    ...
}

To setup the binding between the object and the view, we

  1. Let the User object conform to the ObservableObject protocol.
  2. Wrap those properties, that should cause an observation notification when modified, with @Published.
  3. Wrap the user property in our view with StateObject or @ObservedObject.

From now on, SwiftUI will automatically update the view, when the firstName or the lastName of the User object change.

The difference between StateObject and ObservedObject is the same as between State and Binding. While StateObject is used for the view's private state, ObservedObject creates a two-way connection between a view and a state property defined outside of that view. The view should not create the instance of the ObservedObject itself.

EnvironmentObject

EnvironmentObject is used in the same situations as ObservedObject with the difference, that we don't need to pass it in through the whole view hierarchy.

It is useful for complex view hierarchies where we would have to pass the ObservableObject through several view's initializers before it reaches the view where it's needed. With EnvironmentObject, we only need supply the environment object within one of the view’s parents. SwiftUI will take care of the rest.

struct ExampleView: View {
    @EnvironmentObject var user: User
}

SomeParentViewOfExampleView()
    .environmentObject(user)

We could even make an EnvironmentObject available in the whole app by passing it into the root view of the app.

Combining SwiftUI and UIKit

Until SwiftUI came out, UIKit was the way to build views in iOS. So now, when we start using SwiftUI in existing projects, we need a way to combine those two worlds. The good news is, Apple provides tools to place UIKit views and view controllers inside SwiftUI views, and vice versa.

Using UIKit views inside SwiftUI views

To use a UIKit view in a SwiftUI view, we wrap the UIKit view in a SwiftUI view that conforms to the UIViewRepresentable protocol.

struct Label: UIViewRepresentable {
    @Binding var title: String

    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        return label
    }

    func updateUIView(_ label: UILabel, context: Context) {
        label.text = title
    }
}

When implementing the UIViewRepresentable protocol, we need to implement the two methods makeUIView and updateUIView. In the makeUIView method, we can create and return our UIKit view. The updateUIView gets called from SwiftUI every time there are state changes, so we can update our UIKit view manually.

In SwiftUI, there is no concept of a view controller, everything is a view. So we can use the same approach from above for using UIKit view controllers in SwiftUI views. Only this time, the SwiftUI view needs to conform to the UIViewControllerRepresentable protocol which has equivalent methods as the UIViewRepresentable protocol.

Using SwiftUI views inside UIKit views

For using SwiftUI views inside UIKit, Apple provides a class named UIHostingViewController. We simply initialize a UIHostingController with the SwiftUI view and then use it like a standard view controller.

let vc = UIHostingController(rootView: Text("Hi"))

Conclusion

Just like the introducement of Swift back then, SwiftUI brings a whole new exciting world into iOS development. It might seem overwhelming at first, but in the end, SwiftUI delivers an innovative and simple way to build user interfaces taking advantage of the entire power of Swift.

Written by

Natascha Fadeeva
Author and creator of this site

Contact

Image Credits

Image credits to Krzysztof Dzwonek from Pixabay