WWDC 20
App essentials in SwiftUI
You can now build the entire app using just SwiftUI
New APIs for defining apps and their scenes and views
Defining apps & scenes
An app consists of one or more "scenes"
→ see "Introducing Multiple Windows on iPad" from 2019 for an introduction to the UIKit scenes API
A scene roughly maps to a window:
- on iOS, watchOS and tvOS the app can present one scene at a time
- on iPadOS you can see multiple scenes from the same or different apps side by side
- on macOS, each scene is displayed in a different window or inside tabs within a window
Each scene contains a view hierarchy and/or a hierarchy of child scenes
An app declaration looks similar to a view declaration:
- it's also a lightweight struct
- it implements the
App
protocol similar toView
- the struct can include properties defining data dependencies, marked with wrappers like
@State
- the protocol requires a single
body
property which returnssome Scene
- inside the body definition, a DSL similar to the one for views is used to define the scene hierarchy:
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListView(store: store) } } }
The @main
attribute is a new feature of Swift 5.3 – it allows a type to serve as the program entry point
This replaces the @NSApplicationMain
/@UIApplicationMain
or main.swift
file
The code above is a complete declaration of the app structure
Even though it's just a few lines of code, this automatically provides quite a lot of functionality for free
Window Group
The most common type of scene is a WindowGroup
, which defines the primary interface (main view) of your app
A WindowGroup
displays its view in the expected way or each platform:
- on iPhone, tvOS and watchOS it's shown as a single full-screen window
- on the iPad it supports the new multi-window scene system
- on the Mac it creates multiple Mac windows
The lifecycle of scenes depends on the platform
On iPadOS, the app switcher shows the app name and an optional scene title above each scene/window – this can be the document title, title of the open web page etc.
In latest SwiftUI, you can set that scene title using the new .navigationTitle("Name")
view modifier, which replaces .navigationBarTitle
(it sets both the title seen in the navigation bar and the scene name in the app switcher)
On the Mac, WindowGroup
automatically provides a "File > New Window" command that opens a new window and a Window menu that lists all windows
The scene title is used as the window title in the title bar and the Window menu
Scene windows can also me merged into a single window with tabs by choosing Window > Merge All Windows
Scene storage
@SceneStorage
is a new property wrapper that helps you manage persistence and restoration of view state in scenes
Provide a unique key under which the given value should be saved, and it will be saved and restored at appropriate times automatically
struct ReadingListViewer: View { @SceneStorage("selectedItem") var selectedItem: String? var body: some View { NavigationView { ReadingList(selectedItem: $selectedItem) } } }
Document Group
Another basic kind of scene in the app definition is DocumentGroup
, which defines documents in a document-based app:
@main struct ShapeEditApp: App { var body: some Scene { DocumentGroup(newDocument: SketchDocument()) { file in DocumentView(file.$document) } } }
See more in "Build document-based apps in SwiftUI"
Preferences windows
The last scene type, Settings
, is Mac-only and defines standard Mac Preferences windows:
var body: some Scene { WindowGroup { ... } Settings { BooksSettingsView() } }
The Preferences window is automatically linked to from the main app menu with the standard Cmd+,
keyboard shortcut
Menu commands
The new .commands
view modifier allows you to define menus and menu items with shortcuts for the macOS app menu:
WindowGroup { ... } .commands { BookCommands() }
struct BookCommands: Commands { @FocusedBinding(\.selectedBook) private var selectedBook: Book? var body: some Commands { CommandMenu("Book") { Section { Button("Update Progress...", action: updateProgress) .keyboardShortcut("u") Button("Mark Completed", action: markCompleted) .keyboardShortcut("C") } .disabled(selectedBook == nil) } } private func updateProgress() { selectedBook?.recordNewProgress() } private func markCompleted() { selectedBook?.markCompleted() } }
The commands are also used on the iPad and are displayed in the keyboard shortcuts help popup
The @FocusedBinding
property wrapper used in this commands block allows you to selectively enable/disable some groups of commands based on your focus, similar to how the responder chain is used in AppKit or UIKit
See more about commands in the reference documentation