While it is possible to integrate SwiftUI into a UIKit or AppKit application, this series focuses on building applications that are built entirely using SwiftUI.

Creating a SwiftUI Application

Fire up Xcode and create a project by choosing New > Project... from Xcode's File menu. Select the App template from the Multiplatform > Application section and click Next.

Creating a SwiftUI Application

Name the project Notes. A multiplatform application automatically uses SwiftUI for its user interface. Leave the checkboxes at the bottom unchecked and click Next. Tell Xcode where you would like to save the project and click Create.

Creating a SwiftUI Application

Exploring a SwiftUI Application

Notice how few files the project contains. None of the file names has a controller suffix and the project doesn't contain a storyboard. The project contains NotesApp.swift and ContentView.swift, and an asset catalog for the application's resources. That's it.

Xcode automatically opens ContentView.swift. It displays the file's contents on the left and shows a preview on the right. Click the Resume button in the top right to preview the user interface ContentView defines.

Previewing a SwiftUI Application

Open NotesApp.swift and take a moment to inspect its contents. The familiar import statement for UIKit is replaced with an import statement for SwiftUI. The file declares a struct with name NotesApp that conforms to the App protocol.

import SwiftUI

@main
struct NotesApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Before we take a look at the NotesApp struct, I want to draw your attention to the main attribute that precedes the NotesApp declaration. The main attribute was introduced in Swift 5.3. The Swift Evolution proposal discusses the main attribute in more detail.

The idea is simple, though. Every program needs an entry point and the main attribute indicates which type provides that entry point. The main attribute is applied to a type declaration, the NotesApp struct in this example. By applying the main attribute, you indicate that the NotesApp struct provides the entry point for the program.

A type annotated with the main attribute is required to implement a type function with name main(), accepting no arguments and returning Void. We don't need to worry about this requirement because the App protocol, to which NotesApp conforms, takes care of that. That is the main attribute in a nutshell.

As the name suggests, a type that conforms to the App protocol is a type that defines an application. The body property is a required property of the App protocol. It is of type some Scene and defines the application's form and function. Note that the return type of body is prefixed with the some keyword. What is that about?

The some keyword was introduced in Swift 5.1. The Swift Evolution proposal discusses the some keyword in more detail. What you need to know for now is that body returns an object that conforms to the Scene protocol. The some keyword indicates that body returns an opaque type, which means that the concrete type of body is hidden. In other words, the body property doesn't expose the concrete type it returns other than that it conforms to the Scene protocol. The concrete type is private to the body property. Don't worry if this is confusing. Opaque types are a more advanced feature of the Swift language. What you need to remember is that body returns a scene, an object that conforms to the Scene protocol.

The body property of NotesApp returns a WindowGroup object. As you may have guessed, WindowGroup conforms to the Scene protocol. A window group is nothing more than a container for the view hierarchy of the application. You are not required to return a WindowGroup object. The body property can return any object that conforms to the Scene protocol. For example, the body property of a document-based application returns a DocumentGroup object.

The initializer of WindowGroup accepts a closure as its only argument. This is a common pattern in SwiftUI applications. The closure that is passed to the initializer returns the contents of the scene. For WindowGroup, that object is a view, an object conforming to the View protocol. Whenever I refer to a view in this series, I refer to an object that conforms to the View protocol. We take a closer look at views in the next episode.

The view the closure returns is an instance of ContentView. ContentView is defined in ContentView.swift. Let's take a look at its implementation.

import SwiftUI

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

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

We see another import statement for SwiftUI at the top. ContentView is a struct, a value type, that conforms to the View protocol. The only requirement of the View protocol is the body property. The difference with the Scene protocol is that the body property of the View protocol is of type some View. Note that the return type of body is also prefixed with the some keyword. We discussed the some keyword earlier in this episode.

The body property of ContentView returns another view. In this example, it returns a Text object. Text also conforms to the View protocol and displays a string, Hello, world! in this example. You can ignore the padding() method for now.

Running a SwiftUI Application

Select a simulator from the list of simulators and run the application. The application is as simple as it gets. It displays a single line of text in the center of the screen, Hello, world!.

Running a SwiftUI Application in a Simulator

Because Notes is a multiplatform application, we can run it on macOS. Select the scheme for macOS and run the application. The user interface is basic, but it is nice to see that we are able to run the application on iOS and macOS without any code changes.

Running a SwiftUI Application on a Mac

Applications, Scenes, and Views

Before we move on, I want to revisit applications, scenes, and views. It is important that you understand how they work together. Let's start with views.

A view defines a piece of your application's user interface. Everything your application displays is in some way tied to a view. The text the Notes application displays is a view. A view's body defines the form and function of the view. We discuss views in more detail in the next episode.

A scene groups one or more views. It is the container for a view hierarchy. Revisit NotesApp.swift. WindowGroup conforms to the Scene protocol. The closure that is passed to the initializer of WindowGroup returns the view hierarchy it manages. Each scene has its own state. Take Safari on macOS as an example. Each tab or window of Safari is a scene and each scene has its own state.

A collection of scenes is managed by an application. A single instance of the Safari application runs, but it can manage multiple scenes, that is, multiple tabs and windows. Scenes are the contents of an application. Some applications support multiple scenes and on macOS and iPadOS, it is possible to show multiple scenes at the same time.

If you want to learn more about scenes and scene-based applications, I recommend taking a look at Understanding Scene-Based Applications. That series covers the topic in more detail.

What's Next?

We haven't written a single line of code and yet we have an application that runs on iOS, macOS, and iPadOS. The building blocks you use to build your application's user interface are the same, regardless of the platform you are targeting. This saves time and it results in a consistent user experience across platforms. In the next episode, we take a close look at views, the fundamental building block of a SwiftUI application.