#WWDC19
SwiftUI On All Devices
Jeff Nadeau, macOS Frameworks
Ada Turner, tvOS Frameworks
Meghna Sapre, watchOS Frameworks
© 2019 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
The shortest path to building great
apps on every device
The shortest path to building great
apps on every device
The shortest path to building great
apps on every device
AppKit UIKit TVUIKit WatchKit
UIKit
AppKit UIKit TVUIKit WatchKit
UIKit
AppKit UIKit TVUIKit WatchKit
UIKit
Designed for Every Platform
Designed to accommodate many UI paradigms
Platform support out of the box
A common toolkit that you can learn once and use anywhere
Common Elements
Basic controls
iOS Bluetooth
macOS Bluetooth
tvOS Bluetooth On
watchOS Bluetooth
Common Elements
Basic controls
Toggle(isOn: $btEnabled) {
Text("Bluetooth")
} iOS Bluetooth
macOS Bluetooth
tvOS Bluetooth On
watchOS Bluetooth
Common Elements
Layout
HStack {
Text("Label")
Spacer()
Button(action: /* */) {
Label Delete Cancel
Text("Delete")
}
Button(action: /* */) {
Text("Cancel")
}
}
.padding()
Common Elements
Advanced controls
Picker(selection: /* */, label: /* */) { macOS
ForEach(allFruits) { fruit in
Text(verbatim: fruit.name)
}
} iOS
watchOS
Common Elements
Advanced controls
Picker(selection: /* */, label: /* */) { macOS
ForEach(allFruits) { fruit in
Text(verbatim: fruit.name)
}
} iOS
watchOS
SwiftUI Essentials WWDC 2019
Of course not
One Size Does Not Fit All
One Size Does Not Fit All
There’s no such thing as a “one size fits all app”
One Size Does Not Fit All
There’s no such thing as a “one size fits all app”
Make the most of each platform’s strengths
One Size Does Not Fit All
There’s no such thing as a “one size fits all app”
Make the most of each platform’s strengths
Share skillset and toolset
One Size Does Not Fit All
There’s no such thing as a “one size fits all app”
Make the most of each platform’s strengths
Share skillset and toolset
Share code where it makes sense
Write once, run anywhere
Write once, run anywhere
Learn once, apply anywhere
Learn once, apply anywhere
We built an app
We built four apps
Landmarks
Landmarks
Research and visit landmarks
Landmarks
Research and visit landmarks
Look at photos and visitor info
Landmarks
Research and visit landmarks
Look at photos and visitor info
Get maps and directions
Landmarks
Research and visit landmarks
Look at photos and visitor info
Get maps and directions
Mark favorites
Landmarks for Apple TV
Landmarks for Apple TV
Couch surfing for places to go
Landmarks for Apple TV
Couch surfing for places to go
Together with family or friends
Landmarks for Apple TV
Couch surfing for places to go
Together with family or friends
Mark favorites to research later
Landmarks for Mac
Landmarks for Mac
Research many options in detail
Landmarks for Mac
Research many options in detail
Compare information
Landmarks for Mac
Research many options in detail
Compare information
Sort and filter
Landmarks for Mac
Research many options in detail
Compare information
Sort and filter
Read up on the details
Landmarks for iOS
Landmarks for iOS
Quick information about each landmark
Landmarks for iOS
Quick information about each landmark
Get driving directions
Landmarks for iOS
Quick information about each landmark
Get driving directions
Place a phone call
Landmarks for Apple Watch
Landmarks for Apple Watch
Get at-a-glance info about the current location
Landmarks for Apple Watch
Get at-a-glance info about the current location
Receive important notifications
Learning Experience
Learning Experience
New documentation series
Learning Experience
New documentation series
Build the app step-by-step
Learning Experience
New documentation series
Build the app step-by-step
Available for download today
•
SwiftUI on Apple TV
Ada Turner, tvOS Frameworks
The biggest, boldest screen
The Biggest, Boldest Screen
The Biggest, Boldest Screen
10-foot experience
The Biggest, Boldest Screen
10-foot experience
Focus and the Siri Remote
The Biggest, Boldest Screen
10-foot experience
Focus and the Siri Remote
Streamlined navigation
What is a 10-foot experience?
10-Foot Experience
10-Foot Experience
Large screen
10-Foot Experience
Large screen
Long viewing distance
10-Foot Experience
Large screen
Long viewing distance
Extended periods of use
10-Foot Experience
Large screen
Long viewing distance
Extended periods of use
Multiple viewers at the same time
Landmarks — What Makes the Cut?
Landmarks — What Makes the Cut?
Beautiful photos of landmarks
Landmarks — What Makes the Cut?
Beautiful photos of landmarks
Adding favorites
Landmarks — What Makes the Cut?
Beautiful photos of landmarks
Adding favorites
Basic tourism information
Landmarks — What Makes the Cut?
Beautiful photos of landmarks Lengthy historical information
Adding favorites
Basic tourism information
Landmarks — What Makes the Cut?
Beautiful photos of landmarks Lengthy historical information
Adding favorites Advanced sorting and filtering
Basic tourism information
Landmarks — What Makes the Cut?
Beautiful photos of landmarks Lengthy historical information
Adding favorites Advanced sorting and filtering
Basic tourism information Location-based notifications
Focus and the Siri Remote
Focus and the Siri Remote
Focus and the Siri Remote
Entire interface must support focus
Focus and the Siri Remote
Entire interface must support focus
SwiftUI supports focus by default
// Custom Focusable Views
struct MyFocusableView: View {
let canBecomeFocused: Bool
var body: some View {
return Text("Hello WWDC")
.focusable(canBecomeFocused) { isFocused in
// Focus changed
}
.onPlayPauseCommand {
// Play/pause button pressed
}
.onExitCommand {
// Menu button pressed
}
}
}
// Custom Focusable Views
struct MyFocusableView: View {
let canBecomeFocused: Bool
var body: some View {
return Text("Hello WWDC")
.focusable(canBecomeFocused) { isFocused in
// Focus changed
}
.onPlayPauseCommand {
// Play/pause button pressed
}
.onExitCommand {
// Menu button pressed
}
}
}
// Custom Focusable Views
struct MyFocusableView: View {
let canBecomeFocused: Bool
var body: some View {
return Text("Hello WWDC")
.focusable(canBecomeFocused) { isFocused in
// Focus changed
}
.onPlayPauseCommand {
// Play/pause button pressed
}
.onExitCommand {
// Menu button pressed
}
}
}
// Custom Focusable Views
struct MyFocusableView: View {
let canBecomeFocused: Bool
var body: some View {
return Text("Hello WWDC")
.focusable(canBecomeFocused) { isFocused in
// Focus changed
}
.onPlayPauseCommand {
// Play/pause button pressed
}
.onExitCommand {
// Menu button pressed
}
}
}
Streamlined navigation
// TabbedView and NavigationView on tvOS
struct MainView: View {
enum Tab { case explore, hikes, tours }
@State var selection: Tab
var body: some View {
return NavigationView {
TabbedView(selection: $selection) {
ExploreView().tabItemLabel(Text("Explore")).tag(Tab.explore)
HikesView().tabItemLabel(Text("Hikes")).tag(Tab.hikes)
ToursView().tabItemLabel(Text("Tours")).tag(Tab.tours)
}
}
}
}
// TabbedView and NavigationView on tvOS
struct MainView: View {
enum Tab { case explore, hikes, tours }
@State var selection: Tab
var body: some View {
return NavigationView {
TabbedView(selection: $selection) {
ExploreView().tabItemLabel(Text("Explore")).tag(Tab.explore)
HikesView().tabItemLabel(Text("Hikes")).tag(Tab.hikes)
ToursView().tabItemLabel(Text("Tours")).tag(Tab.tours)
}
}
}
}
// TabbedView and NavigationView on tvOS
struct MainView: View {
enum Tab { case explore, hikes, tours }
@State var selection: Tab
var body: some View {
return NavigationView {
TabbedView(selection: $selection) {
ExploreView().tabItemLabel(Text("Explore")).tag(Tab.explore)
HikesView().tabItemLabel(Text("Hikes")).tag(Tab.hikes)
ToursView().tabItemLabel(Text("Tours")).tag(Tab.tours)
}
}
}
}
// TabbedView and NavigationView on tvOS
struct MainView: View {
enum Tab { case explore, hikes, tours }
@State var selection: Tab
var body: some View {
return NavigationView {
TabbedView(selection: $selection) {
ExploreView().tabItemLabel(Text("Explore")).tag(Tab.explore)
HikesView().tabItemLabel(Text("Hikes")).tag(Tab.hikes)
ToursView().tabItemLabel(Text("Tours")).tag(Tab.tours)
}
}
}
}
tvOS vs. iOS Navigation Hierarchies
tvOS vs. iOS Navigation Hierarchies
iOS
TabbedView
tvOS vs. iOS Navigation Hierarchies
iOS
TabbedView
NavigationView Content NavigationView
tvOS vs. iOS Navigation Hierarchies
iOS
TabbedView
NavigationView Content NavigationView
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS
TabbedView
NavigationView Content NavigationView
Content Content
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS
TabbedView
NavigationView Content NavigationView
Content Content
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView NavigationView
NavigationView Content NavigationView
Content Content
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView NavigationView
NavigationView Content NavigationView TabbedView
Content Content
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView NavigationView
NavigationView Content NavigationView TabbedView
Content Content Content Content Content
Content Content
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView NavigationView
NavigationView Content NavigationView TabbedView
Content Content Content Content Content
Content Content Content Content Content
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView(selection: $selection) { NavigationView {
NavigationView { ExploreView() } TabbedView(selection: $selection) {
.tabItemLabel(Text("Explore")) ExploreView()
.tag(Tab.explore) .tabItemLabel(Text("Explore"))
NavigationView { HikesView() } .tag(Tab.explore)
.tabItemLabel(Text("Hikes")) HikesView()
.tag(Tab.hikes) .tabItemLabel(Text("Hikes"))
NavigationView { ToursView() } .tag(Tab.hikes)
.tabItemLabel(Text("Tours")) ToursView()
.tag(Tab.tours) .tabItemLabel(Text("Tours"))
} .tag(Tab.tours)
}
}
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView(selection: $selection) { NavigationView {
NavigationView { ExploreView() } TabbedView(selection: $selection) {
.tabItemLabel(Text("Explore")) ExploreView()
.tag(Tab.explore) .tabItemLabel(Text("Explore"))
NavigationView { HikesView() } .tag(Tab.explore)
.tabItemLabel(Text("Hikes")) HikesView()
.tag(Tab.hikes) .tabItemLabel(Text("Hikes"))
NavigationView { ToursView() } .tag(Tab.hikes)
.tabItemLabel(Text("Tours")) ToursView()
.tag(Tab.tours) .tabItemLabel(Text("Tours"))
} .tag(Tab.tours)
}
}
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView(selection: $selection) { NavigationView {
NavigationView { ExploreView() } TabbedView(selection: $selection) {
.tabItemLabel(Text("Explore")) ExploreView()
.tag(Tab.explore) .tabItemLabel(Text("Explore"))
NavigationView { HikesView() } .tag(Tab.explore)
.tabItemLabel(Text("Hikes")) HikesView()
.tag(Tab.hikes) .tabItemLabel(Text("Hikes"))
NavigationView { ToursView() } .tag(Tab.hikes)
.tabItemLabel(Text("Tours")) ToursView()
.tag(Tab.tours) .tabItemLabel(Text("Tours"))
} .tag(Tab.tours)
}
}
tvOS vs. iOS Navigation Hierarchies
iOS tvOS
TabbedView(selection: $selection) { NavigationView {
NavigationView { ExploreView() } TabbedView(selection: $selection) {
.tabItemLabel(Text("Explore")) ExploreView()
.tag(Tab.explore) .tabItemLabel(Text("Explore"))
NavigationView { HikesView() } .tag(Tab.explore)
.tabItemLabel(Text("Hikes")) HikesView()
.tag(Tab.hikes) .tabItemLabel(Text("Hikes"))
NavigationView { ToursView() } .tag(Tab.hikes)
.tabItemLabel(Text("Tours")) ToursView()
.tag(Tab.tours) .tabItemLabel(Text("Tours"))
} .tag(Tab.tours)
}
}
•
Demo
•
SwiftUI on Mac
Jeff Nadeau, macOS Frameworks
•
High information density
•
Multiple windows
•
Keyboard shortcuts
•
Touch Bar
•
High information density
•
Multiple windows
•
Keyboard shortcuts
•
Touch Bar
High Information Density
Provide more information at a glance
High Information Density
Provide more information at a glance
Precision pointing device allows smaller controls
High Information Density
Provide more information at a glance
Precision pointing device allows smaller controls
High Information Density
Provide more information at a glance
Precision pointing device allows smaller controls
Higher tolerance for large amounts of text
High Information Density
Provide more information at a glance Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Curabitur consectetur tincidunt mollis. Vivamus
ipsum nulla, sagittis id pellentesque ac, suscipit in
Precision pointing device allows smaller controls dui. Donec pretium suscipit blandit. Donec volutpat
mollis nulla. Proin a dapibus lorem. Morbi id tempor
nunc. Mauris tempus lacinia ligula in faucibus.
Higher tolerance for large amounts of text Quisque nec maximus augue. Nunc eu ornare nulla.
Suspendisse quam est, faucibus nec imperdiet sed,
sagittis at purus.
Nam mollis, lorem sed pretium efficitur, mauris leo
luctus elit, volutpat consectetur turpis libero vel
ante. Nulla non malesuada leo. Aenean ut interdum
turpis. Nunc sit amet euismod lorem, suscipit
venenatis nibh. Vestibulum euismod facilisis nisl nec
tincidunt. Integer massa ex, commodo vel finibus id,
cursus id erat. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae;
Maecenas ligula ante, facilisis id tristique nec,
eleifend vitae neque.
High Information Density
High Information Density
Spacing and padding automatically adapt
High Information Density
Spacing and padding automatically adapt
Use .controlSize() for compact controls
•
High information density
•
Multiple windows
•
Keyboard shortcuts
•
Touch Bar
Multiple Windows
Multiple Windows
Compare content across windows side-by-side
Multiple Windows
Compare content across windows side-by-side
Focus on a single item in its own window
Multiple Windows
Compare content across windows side-by-side
Focus on a single item in its own window
Spatially organize windows around the Desktop and Spaces
•
High information density
•
Multiple windows
•
Keyboard shortcuts
•
Touch Bar
Keyboard Shortcuts
Keyboard Shortcuts
Quick access to common actions
Keyboard Shortcuts
Quick access to common actions
Lightning-fast navigation
// Adding Keyboard Shortcuts
TabbedView(selection: $selectedTab) {
ExploreView().tag(Tab.explore).tabItemLabel(Text("Explore"))
HikesView().tag(Tab.hikes).tabItemLabel(Text(“Hikes"))
ToursView().tag(Tab.tours).tabItemLabel(Text(“Tours"))
}
Keyboard Shortcuts
Keyboard Shortcuts
extension Command {
static let showExplore = Command(Selector(("showExplore:")))
static let showHikes = Command(Selector(("showHikes:")))
static let showTours = Command(Selector(("showTours:")))
}
// Adding Keyboard Shortcuts
TabbedView(selection: $selectedTab) {
ExploreView().tag(Tab.explore).tabItemLabel(Text("Explore"))
HikesView().tag(Tab.hikes).tabItemLabel(Text(“Hikes"))
ToursView().tag(Tab.tours).tabItemLabel(Text(“Tours"))
}
// Adding Keyboard Shortcuts
TabbedView(selection: $selectedTab) {
ExploreView().tag(Tab.explore).tabItemLabel(Text("Explore"))
HikesView().tag(Tab.hikes).tabItemLabel(Text(“Hikes"))
ToursView().tag(Tab.tours).tabItemLabel(Text(“Tours"))
}
.onCommand(.showExplore) { self.selectedTab = .explore }
.onCommand(.showHikes) { self.selectedTab = .hikes }
.onCommand(.showTours) { self.selectedTab = .tours }
// Adding Keyboard Shortcuts
TabbedView(selection: $selectedTab) {
ExploreView().tag(Tab.explore).tabItemLabel(Text("Explore"))
HikesView().tag(Tab.hikes).tabItemLabel(Text(“Hikes"))
ToursView().tag(Tab.tours).tabItemLabel(Text(“Tours"))
}
.onCommand(.showExplore) { self.selectedTab = .explore }
.onCommand(.showHikes) { self.selectedTab = .hikes }
.onCommand(.showTours) { self.selectedTab = .tours }
// Adding Keyboard Shortcuts
TabbedView(selection: $selectedTab) {
ExploreView().tag(Tab.explore).tabItemLabel(Text("Explore"))
HikesView().tag(Tab.hikes).tabItemLabel(Text(“Hikes"))
ToursView().tag(Tab.tours).tabItemLabel(Text(“Tours"))
}
.onCommand(.showExplore) { self.selectedTab = .explore }
.onCommand(.showHikes) { self.selectedTab = .hikes }
.onCommand(.showTours) { self.selectedTab = .tours }
Integrating SwiftUI WWDC 2019
•
High information density
•
Multiple windows
•
Keyboard shortcuts
•
Touch Bar
// Adding Touch Bar Support
TouchBar {
}a
// Adding Touch Bar Support
TouchBar {
Button(action: toggleFavorite) {
Image(isFavorite ? "favorite" : "favorite-empty")
}a
}a
// Adding Touch Bar Support
TouchBar {
Button(action: toggleFavorite) {
Image(isFavorite ? "favorite" : "favorite-empty")
}a
Button(action: placeCall) {
Image("call")
}a
}a
// Adding Touch Bar Support
TouchBar {
Button(action: toggleFavorite) {
Image(isFavorite ? "favorite" : "favorite-empty")
}a
Button(action: placeCall) {
Image("call")
}a
Button(action: getDirections) {
Text("Get Directions")
}a
}a
// Adding Touch Bar Support
MyCustomView() .touchBar(
TouchBar {
Button(action: toggleFavorite) {
Image(isFavorite ? "favorite" : "favorite-empty")
}a
Button(action: placeCall) {
Image("call")
}a
Button(action: getDirections) {
Text("Get Directions")
}a
}a
)
// Adding Touch Bar Support
MyCustomView() .touchBar(
TouchBar {
Button(action: toggleFavorite) {
Image(isFavorite ? "favorite" : "favorite-empty")
}a
Button(action: placeCall) {
Image("call")
}a
Button(action: getDirections) {
Text("Get Directions")
}a
}a
)
•
Demo
•
SwiftUI on Apple Watch
Meghna Sapre, watchOS Frameworks
•
watchOS App
•
Interactive Notifications
•
Landmarks on Apple Watch
3
Taps or fewer
watchOS App
watchOS App
ScrollView
watchOS App
watchOS App
.digitalCrownRotation
watchOS App
.digitalCrownRotation
watchOS App
watchOS App
HStack
VStack
watchOS App
watchOS App
List
Section
•
watchOS App
•
Interactive Notifications
•
Landmarks on Apple Watch
Interactive Notifications
Interactive Notifications
Provide useful information
Interactive Notifications
Provide useful information
Offer intuitive actions
Interactive Notifications
Provide useful information
Offer intuitive actions
Deliver at the right time
•
watchOS App
•
Interactive Notifications
•
Landmarks on Apple Watch
Landmarks on Apple Watch
Landmarks on Apple Watch
Landmarks on Apple Watch
Landmarks on Apple Watch
Landmarks on Apple Watch
ForEach(landmarks) { landmark in
WatchLandmarkDetails(landmark: landmark)
}
Landmarks on Apple Watch
// WatchLandmarkDetails
// Image
LandmarkImage(landmark: landmark)
// Details
NavigationButton(destination: WatchTourDetails(landmark:
landmark)) {
Text("Tour: 10:00 AM")
}
// Make a Call
Button(action: call) {
Image(systemName: "phone.fill")
.foregroundColor(.green)
}
Landmarks on Apple Watch
Landmarks on Apple Watch
Landmarks on Apple Watch
var landmarks: [Landmark] {
if showFavoritesOnly {
return landmarkData.filter(isFavorite)
}
return landmarkData
}
Landmarks on Apple Watch
var landmarks: [Landmark] {
if showFavoritesOnly {
return landmarkData.filter(isFavorite)
}
return landmarkData
}
Button(action: self.toggleFavoritesOnly) {
Text(self.userData.showFavoritesOnly ?
"Show All" : "Show Favorites")
}
Landmarks on Apple Watch
return landmarkList
Landmarks on Apple Watch
return landmarkList
Landmarks on Apple Watch
return landmarkList
.listStyle(.carousel)
Interactive Notifications
Interactive Notifications
•
Demo
Summary
Summary
Take a design-first approach
Summary
Take a design-first approach
Effortlessly share model code
Summary
Take a design-first approach
Effortlessly share model code
Be judicious when sharing view code
Summary
Take a design-first approach
Effortlessly share model code
Be judicious when sharing view code
Learn once, apply anywhere
More Information
developer.apple.com/wwdc19/240