WWDC 20
What's new in SwiftUI
App & scene definition:
You can now build the whole app with SwiftUI, including what was previously handled by the application delegate and/or window controllers
The code below is a complete SwiftUI app:
@main struct HelloWorld: App { var body: some Scene { WindowGroup { Text("Hello world!") } } }
The API for defining apps is intentionally similar to the one for defining views:
- the app definition is also a struct
- it implements the
App
protocol similar to theView
protocol, also with a requiredbody
property which is a function builder - the struct may have some properties marked with wrappers like
@State
The body of an App
returns a Scene
, which is a new concept: it represents a piece of the app's user interface that can be independently displayed by the platform
The primary type of a Scene
is a WindowGroup
A WindowGroup
renders as a single full-screen window on iPhone and Watch, on iPad it supports the new multi-window scene system, and on the Mac it creates multiple Mac windows
Another scene type is Settings
on the Mac, which creates Mac preferences windows:
#if os(macOS) Settings { PreferencesList() } #endif
The preferences window is automatically styled as expected and linked to from a shortcut in the app menu
For document-based apps, use DocumentGroup
which automatically manages documents:
@main struct ShapeEditApp: App { var body: some Scene { DocumentGroup(newDocument: SketchDocument()) { file in DocumentView(file.$document) } } }
On iOS, it automatically presents a document browser screen
On the Mac, it opens different documents in separate windows and adds a File menu with all standard commands like Open/Save
You can also use the .commands
view modifier to define custom menu commands:
.commands { CommandMenu("Shape") { Button("Add Shape", action: { ... }) .keyboardShortcut("N") ... } }
Use CommandMenu
to create complete new menus, and CommandGroup
to add single items to system menus like Help:
CommandGroup(after: .help) { Button(...) }
See more about app/scene APIs in "App Essentials in SwiftUI" and "Build Document-Based Apps in SwiftUI"
There's also a new section of multi-platform Xcode project templates that creates SwiftUI apps which build for all platforms from a single codebase
New “Launch Screen” Info.plist
key
- lets you define launch screen elements like background, navigation bar, tab bar
- a replacement for the launch storyboard for SwiftUI apps that don't otherwise have storyboards
iOS 14 also introduces widgets, which are built exclusively with SwiftUI
See more in "Build SwiftUI Views for Widgets" and "Widgets Code-Along"
Complications for Apple Watch can now also be built using SwiftUI
See more in "Build Complications in SwiftUI" and "Creating Complications for Apple Watch"
Improvements to lists and collections
Outlines – hierarchical lists which display items in a collapsible tree structure:
List(graphics, children: \.children) { … }
Grids – collection view-like, lazy-loading, vertically or horizontally scrolled 2D grids of content:
LazyVGrid(columns: [GridItem(.adaptive(minimum: 176))]) { … } -> adapts number of columns to view size LazyVGrid(columns: Array(repeating: GridItem(), count: 4)) { } -> fixed number of columns
New lazy versions of the existing stack views: LazyVStack
, LazyHStack
See more about lists and grids in "Stacks, Grids, and Outlines in SwiftUI"
Toolbars
New common API for constructing toolbars on all platforms:
.toolbar { Button(action: recordProgress) { Label("Record Progress", systemImage: "book.circle") } ToolbarItem(placement: .primaryAction) { Button(...) } }
You can use a Button
to let the system automatically place the toolbar button in an appropriate place, or use ToolbarItem
to customize the placement location using the placement
parameter
ToolbarItem
has various semantic names for placement like principal
, confirmationAction
, bottomBar
etc.
Labels:
Label
is a view that combines an icon with a title and shows them appropriately for the context:
Label("Books", systemImage: "book.circle") // or, the full form: Label { Text("...") } icon: { Image(systemName: "...") }
Depending on where a Label
is used, the system may decide to only show the icon (and use the title for accessibility purposes), only the title, or show them both, arranged properly (also properly aligning multiple label icons in a list, scaling icons depending on dynamic font size, and so on)
Tooltips:
On macOS, you can use the new .help
modifier to add help tooltips to toolbar buttons and other controls:
Button(action: recordProgress) { Label("Record Progress", systemImage: "book.circle") } .help("Record new progress entry")
This is available on all platforms – on other platforms the text is used for accessibility (Voiceover)
Keyboard shortcuts:
New .keyboardShortcut
view modifier for adding keyboard shortcuts
This is mainly used in scene commands for Mac menu items / iOS keyboard shortcuts:
.commands { CommandMenu("Books") { Button("Previous Book", action: selectPrevious) .keyboardShortcut("[") Button("Next Book", action: selectNext) .keyboardShortcut("]") } }
Shortcuts can also be set on other controls within the window content area, e.g. to create default & cancel buttons in Mac alert views:
HStack { Button("Cancel", action: dismissSheet) .keyboardShortcut(.cancelAction) Button("Save", action: saveLink) .keyboardShortcut(.defaultAction) }
Focus
New default focus support on tvOS – the app can control where focus starts on the screen and how focus changes depending on app state
More information in the "Build SwiftUI apps for tvOS" talk
Other new controls
Progress view:
ProgressView("Downloading photo", value: percentComplete)
- shows a determinate or indeterminate progress indicator
- can be configured to display with a linear or circular style
- plain
ProgressView()
creates a spinner
Gauge:
Gauge(value: acidity, in: 3...10) { Label("Soil Acidity", systemImage: "drop.fill") .foregroundColor(.green) } currentValueLabel: { Text("\(acidity, specifier: "%.1f")") } minimumValueLabel: { Text("3") } maximumValueLabel: { Text("10") }
Gauge is a watchOS-specific control, a linear or circular meter which displays the current value of something relative to its possible or expected range (mostly used in complications)
New effects and styling
Matched geometry effect:
Matched geometry is a new animation effect that allows you to fluently transition or morph one view into another view somewhere else in the view hierarchy
To use it, create a "namespace" the two views will share and mark them both with that namespace and a common identifier:
@Namespace private var namespace var body: some View { ... LazyVGrid(...) { ForEach(unselectedAlbums) { album in AlbumCell(album) } .matchedGeometryEffect(id: album.id, in: namespace) } ... HStack { ForEach(selectedAlbums) { album in AlbumCell(album) } .matchedGeometryEffect(id: album.id, in: namespace) } }
Container relative shapes:
This is a new "smart" shape type that takes the shape of the nearest containing shape:
.clipShape(ContainerRelativeShape())
This applies rounded corners that match the ones in the parent view, adjusted by the padding between the views so that the inner corners "fit into" the outer ones
Text scaling improvements:
Custom fonts (.font(.custom(...)
)) now automatically adapt their sizes with Dynamic Type
Images can be embedded inside text and are scaled together with it:
Text("\(Image(systemName: "music.mic")) \(album.artist)")
The new @ScaledMetric
property wrapper can be used to scale any metric of your view with Dynamic Type:
@ScaledMetric private var padding: CGFloat = 10.0 ... Text(...).padding(padding)
This will scale the padding depending on the system font size so that it looks appropriate at all text sizes
See more about text scaling in "The Details of UI Typography"
Accent colors:
Support for accent colors on macOS
App accent color can now be set in the asset catalog in Xcode 12
.listItemTint()
– customizes the tint color for list items – for the whole list, a section, or a specific item
On watchOS this replaces .listRowPlatterColor
You can now override the standard tint color for some controls like buttons and toggles using customized styles:
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
System integration
Link element:
Link(destination: appleURL) { Label("SwiftUI tutorials", systemImage: "swift") }
This creates a link label which opens the given URL in the default browser when tapped/clicked (or opens the relevant app in case of universal links)
openURL function:
There is a new openURL
function stored inside the Environment
that can be used to programmatically open URLs in the browser (like UIApplication.shared.open
):
@Environment(\.openURL) private var openURL var body: some View { Button("Open page", action: { openURL(homepageURL) }) }
Drag & Drop:
Drag & drop APIs are now based on the new UniformTypeIdentifiers
framework, which provides content type identifiers
UTType
objects let you access information about a content type like human readable description or whether it conforms to general types like "image"
Views from specialized frameworks:
A SwiftUI button that launches the "Sign In With Apple" flow:
import AuthenticationServices import SwiftUI SignInWithAppleButton( .signUp, onRequest: handleRequest, onCompletion: handleCompletion ) .signInWithAppleButtonStyle(.black) func onRequest(request: ASAuthorizationAppleIDRequest) { … } func onCompletion(result: Result<ASAuthorization, Error>) { … }
Various new SwiftUI views for: AVKit, MapKit, SceneKit, SpriteKit, QuickLook, HomeKit, ClockKit, StoreKit, WatchKit