WWDC 19
SwiftUI On All Devices
(*) – marks APIs that were changed since the video was published
SwiftUI is the shortest path to building great apps on every device
SwiftUI is designed to accomodate many UI paradigms – it can be used with a mouse, touch, digital crown, remote, or assistive technologies
It has great support for any specific platform’s conventions out of the box
It was built with the help of experts from all devices, to make sure that on each platform it feels right at home
"Learn once and use anywhere"
Common elements that render in an appropriate way on every device
Common layout system that works the same way everywhere
More advanced elements like Picker
, which renders as a scrollable wheel or a button that navigates to a subpage on iOS, and as a popup button or a set of radio buttons on macOS
There’s no such thing as a “one size fits all app” – if you stick to the common denominator across all devices, you’ll be missing out on the things that make each device great, so you have to decide what the right design is for your app on each device and apply platform-specific customizations
But you can share skills and tools and some code *where it makes sense*
SwiftUI really helps you to reuse some parts of view code between platforms by making it very easy to refactor code, extracting parts of the view code into small reusable components that compose together
When porting an app to a new platform, take a design-first approach: don’t just look at how to make the code run on the other platform, think about what is the right expression for your app on this device
tvOS:
Biggest, boldest screen, viewed from a larger distance across the room
Longer periods of use than on mobile
Often used together with friends
Used with Siri Remote, entire interface must be navigable using focus
Streamlined navigation
Rich, immersive experience – media, photos, videos, not performing tasks
Carefully consider which experiences make sense when viewed on a large screen
Here in the Landmarks app, we want to include:
- beautiful photos of landmarks
- adding/removing of favorites
- basic tourism information
We don’t want:
- lengthy historical information (don't show long pieces of text)
- advanced sorting & filtering (we don't want too many controls)
- anything location-based, since people don't carry their TVs around
Focus:
SwiftUI supports focus by default for built-in controls
For more custom views, you can use the .focusable
modifier if the view should be focusable and react to focus:
.focusable(canBecomeFocused) { isFocused … }
Siri Remote buttons:
Handling play/pause and menu buttons on the Siri Remote:
.onPlayPauseCommand { } .onExitCommand { }
Navigation & design:
Don’t use long, vertically scrolled and nested lists of text, use a navigation UI that emphasizes content (pictures)
Take advantage of the big screen
Use horizontal stack views to create vertical lists of horizontally scrolled sections like e.g. in App Store apps, allowing the user to browse different categories while staying on a single page
On iOS, when using both a navigation controller and tab controller, the tab controller is at the top level and a navigation controller may be used in one or more tabs, so that when you navigate deeper the tab bar is still visible
On tvOS, the tab controller should be added *inside* the navigation controller, so that you see tabs at the top on the root level, but when you select something from the list, the tabs disappear to leave more space for the content
macOS:
High information density – a large screen with relatively small fonts, so you can show a lot of information
Higher tolerance for large amounts of text
Precision pointing device allows smaller click targets and denser controls (within reason!)
Multiple window support
Keyboard shortcuts
Touch Bar
In the Landmarks app on the Mac, we use a master-detail view with a list that shows detailed, condensed content, and detailed information about the selected landmark (including longer text) on the right
The app also supports opening multiple landmarks in separate windows by double-clicking rows in the list
SwiftUI automatically adjusts padding/spacing to be appropriate for the Mac
Use .controlSize()
for more compact controls
Menu item commands: (*)
view.onCommand(#selector(showExplore:)) { ... }
ℹ️ Previously this modifier accepted a Command
object in which you had to wrap the selector
Touch Bar:
view.touchBar( TouchBar { Button(action: show) { Image(…) } Button(action: delete) { Text(…) } } )
Customizing parts of the UI:
To reuse the same container view between platforms but customize one of its subviews, e.g. the row view here used for each landmark within the list, you can make the container view generic, parametrized with the row type, and add a “row provider” closure that can be passed to the constructor:
struct SharedLandmarksList<LandmarkRowType: View>: View { var rowProvider: (Landmark) -> LandmarkRowType var body: some View { List(selection: $selectedLandmark) { ForEach(landmarks) { landmark in self.rowProvider(landmark: landmark).tag(landmark) } } } }
Then, use the generic view in a Mac-specific view, passing a closure that gives it a Mac-specific row view for a given landmark:
struct MacLandmarksList: View { var body: some View { SharedLandmarksList() { landmark in return MacLandmarkRow(landmark: landmark) } } }
Handling double-click: (*)
.onTapGesture(count: 2) { … }
ℹ️ Previously this modifier was named .tapAction
watchOS:
A good rule is to aim for important actions to be reachable with 3 taps or less
Don't just shrink your iPhone app, bring only the most relevant parts of it to the user's wrist
In the Watch version of the Landmarks app, we show only the most important information about the landmarks in the list, and include buttons for marking as favorite, calling the park's booking service, and showing navigation directions
We also include a toggle to only show favorite landmarks in order to limit the length of the list
The Watch app also includes interactive notifications when a new event related to a favorite landmark is added
.digitalCrownRotation
modifier – handling digital crown events
.listStyle(.carousel)
(*) – a scrolling list that focuses on each cell
ℹ️ After the initial betas, all control style modifiers like .listStyle
required manual instantion of a style type object, like: .listStyle(CarouselListStyle())
. This was changed again to the simplified version like above in Xcode 15 thanks to some changes in Swift generics.