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.
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.
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.
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!.
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.
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.