SwiftUI Fundamentals

What Is a View

SwiftUI Fundamentals
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

A view is the fundamental building block of your application's user interface. You already know that a view is a type that conforms to the View protocol. In this episode, we take a closer look at views and the View protocol.

What Is a View

Let's revisit the project we created in the previous episode. Open ContentView.swift and inspect the implementation of the ContentView struct. ContentView is a view because it conforms to the View protocol. As a view, ContentView needs to meet the requirements of the View protocol. First, it defines a computed body property. You learned in the previous episode that body defines the contents and behavior of a view.

Second, a view defines an associated type for the computed body property. Let's take a look at the View protocol to understand what that means. Notice that body is of type Self.Body and that the View protocol defines an associated type with name Body. The only requirement of the associated type is that it conforms to the View protocol.

It isn't as complex as it sounds, though. Let's revisit the implementation of ContentView. Notice that ContentView doesn't explicitly define the associated type. The compiler inspects the return type of the computed body property and infers the associated type. The only requirement is that the associated type conforms to the View protocol. It meets that requirement since the computed body property is of type some View.

Don't worry if this is confusing. What you need to remember is that a view conforms to the View protocol and that it needs to implement a computed body property that returns another view.

Composing Views

A view is a piece of your application's user interface. By composing views, you can build the user interface you have in mind for your application. It can be as simple or as complex as you like. Take a list view as an example. A list view displays one or more cells and each cell is composed of one or more views. The smallest unit of your application's user interface is a view, regardless of the complexity of the user interface.

You already know that a view is required to have a computed body property that returns another view. This may seem odd and recursive. Right? To avoid recursion, some views don't return another view. Those views are primitive views.

The SwiftUI framework defines a broad range of primitive views. Text, for example, doesn't return a view. It draws the string it is given to the screen. Those primitive views are the building blocks you can use in the custom views you create to build your application's user interface.

Views Are Lightweight

Any type can conform to the View protocol, but views should lightweight. That is why it is recommended for views to be structs, that is, value types. It is strongly recommended to avoid performing significant work in the view's initializer. In fact, you don't often have a reason to implement a custom initializer since a struct automatically receives a memberwise initializer. Take ContentView as an example. ContentView is a struct that conforms to the View protocol. It is lightweight and inexpensive to create.

The user interface of an application is composed of dozens and dozens of views. That is fine as long as you make sure the views you create are lightweight and inexpensive to create. SwiftUI decides when it creates and destroys views. It performs a range of optimizations to create a user interface that is performant and responsive.

That is one of the niceties of SwiftUI. As a developer, you declare or describe the user interface of your application through composition. SwiftUI translates that description into a user interface and it ensures the user interface always reflects the state of your application.

This is one of the most important aspects of SwiftUI to understand and remember. You declare or describe the user interface of your application. SwiftUI takes care of the rest.

No Controllers

You learned in the previous episode that the template of a SwiftUI application doesn't include a controller. While it may be confusing at first, it starts to make sense once you understand what SwiftUI is and how it works.

In a Model-View-Controller application, the controller creates and manages one or more views. It configures the views and constructs the view hierarchy of the application's user interface. That is what Apple refers to as an imperative approach to user interface design. SwiftUI takes a different approach. As a developer, you declare or describe the user interface and SwiftUI uses that description to create the user interface. This declarative approach ensures the user interface always reflects the application's state. There is no need for a controller in a SwiftUI application.

View Dependencies

SwiftUI allows you to describe the dependencies of a view. What does that mean? ContentView displays a static string. Let's make it a bit more dynamic by declaring a property, title of type String.

import SwiftUI

struct ContentView: View {

    // MARK: - Properties

    let title: String

    // MARK: - View

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

}

The title property defines a dependency of the ContentView struct. This simply means that ContentView depends on the value of the title property to render its contents. In this example, the title property is defined as a constant because it won't change. Later in this series, we use bindings to automatically update the user interface if the application's state changes.

In the computed body property of ContentView, the value of the title property is passed to the initializer of Text.

import SwiftUI

struct ContentView: View {

    // MARK: - Properties

    let title: String

    // MARK: - View

    var body: some View {
        Text(title)
            .padding()
    }

}

Swift automatically creates a memberwise initializer by inspecting the declaration of ContentView. What Is a Memberwise Initializer covers memberwise initializers in more detail. We create a ContentView instance in two places so we need to make two changes. Open NotesApp.swift and pass a string literal to the initializer of ContentView.

import SwiftUI

@main
struct NotesApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView(title: "Welcome to Notes")
        }
    }

}

We also need to update ContentView.swift. The Content_Previews struct creates the preview for the ContentView struct. We cover previews in more detail later in this series. Pass a string literal to the initializer of ContentView and click the Resume button in the top right to preview the changes we made to ContentView.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(title: "Welcome to Notes")
    }
}

Previewing a User Interface Built with SwiftUI

Container Views

There is another category of views you need to become familiar with, container views. The computed body property of ContentView returns a view. You may be wondering how you build complex user interfaces if a view can only return another view. Container views solve that problem. A container view is a view that contains one or more children. We cover container views in more detail later in this series because they are an indispensable aspect of SwiftUI. Let's take a look at an example.

The computed body property of ContentView returns a Text instance. Remove the padding() method for now and add another Text instance below the existing Text instance.

var body: some View {
    Text(title)
    Text("A place to organize your notes.")
}

The compiler doesn't throw an error, but the preview on the right indicates that something isn't quite right. Xcode shows two previews instead of one. The first preview displays Welcome to Notes while the second preview displays A place to organize your notes.

An Introduction to Container Views in SwiftUI

We can solve this problem be embedding the Text instances in a vertical stack, an instance of the VStack struct. Like many other views, the initializer accepts a closure. The closure returns the contents of the vertical stack.

var body: some View {
    VStack {
        Text(title)
        Text("A place to collect your notes.")
    }
}

The preview shows the result of this simple change. The Text instances are stacked vertically and we no longer have two previews.

What's Next?

A view is nothing more than a type that conforms to the View protocol. It describes a piece of your application's user interface. While this isn't a difficult concept to grasp, it is very different from what you are used to. UIKit and AppKit take an imperative approach, which is very different from SwiftUI's declarative approach. Don't worry if some concepts we covered in this episode haven't sunk in yet. We take a close look at each of these concepts in this series.

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy
Next Episode "Working with Xcode Previews"