Streaming is available in most browsers,
and in the Developer app.
-
Update your app for watchOS 10
Join us as we update an Apple Watch app to take advantage of the latest features in watchOS 10. In this code-along, we'll show you how to use the latest SwiftUI APIs to maximize glanceability and reorient app navigation around the Digital Crown.
Chapters
- 1:33 - Build against the watchOS 10 SDK
- 2:33 - NavigationSplitView
- 5:21 - TabView and vertical pagination
- 8:11 - Toolbar with new placement options
- 9:07 - Background color
- 11:10 - Materials
Resources
Related Videos
WWDC23
-
Download
♪ ♪ Nina: Hello. My name is Nina Paripovic, and I'm a watchOS software engineer. In this code-along session, we'll take an existing app built with the watchOS 9 SDK and update it for watchOS 10. We'll talk about the new design paradigms and SwiftUI APIs you can use to make your app look beautiful alongside the system-wide redesign. The sample project is linked in the Resources list associated with this session. I encourage you to start that download now so that we're ready to build for watchOS 10 together. Before we jump in, let's take a look at our existing project. The app we'll be updating is Backyard Birds, an app that lets you create and manage beautiful backyard gardens and their curious visitors. In the app today, there is a list of backyards. Each backyard has a detail view which shows us its activity and condition. If food or water is running low, they can be topped up. We can also see all of our bird visitors in each backyard. watchOS 10 brings an exciting, fresh new look to watch apps. If you haven't already, check out "Meet watchOS 10" as well as "Design and build apps for watchOS 10" to get more info on the design principles we are going to apply in this session. Now, let's open up Xcode to get started with updating our app.
Here is our current Backyard Birds app built for and running on watchOS 9. We see the list of backyards, which transition to a backyard detail. First, let's build against the watchOS10 SDK.
The app is already looking great. We see new system features, like the large title that transitions as we scroll and the new material that blurs content underneath the navigation bar. Tapping on a backyard animates the cell, and we have automatically adopted the systemwide toolbar button styling we see in the top left. If we scroll and then tap on Refill, it presents a modal with our new blurred background material.
People who use Backyard Birds care most about what's happening in their own backyard. Currently, when they launch the app, the first thing they do is tap on a backyard in the list to see what's happening in the detail view. In watchOS 10, we can make things easier by modifying the structure of the app with NavigationSplitView. This will allow people to launch directly into the detail view of their backyard and then pop back up to the list when they want to visit their friends' yards. Since the detail view is the most important part of our app, we can bring emphasis to it by implementing NavigationSplitView.
NavigationSplitView is great when we have this strong source list and detail relationship. It allows people to focus on the detail by directly launching to it, while still being able to easily switch over to another yard from the source list.
We're already using it for Backyard Birds on iOS and iPadOS. Looks like NavigationSplitView would be a great fit for our app.
In ContentView, we are going to swap out the NavigationStack for a NavigationSplitView. Then we are going to move the contents of our navigationDestination into the detail of the NavigationSplitView. Finally, we'll adopt the new list selection APIs, available in watchOS 10, that will drive which detail view is presented by the NavigationSplitView. To start, let's swap out the NavigationStack for a NavigationSplitView.
We can leave the Backyard List where it is at the root of the NavigationSplitView. Let's remove the navigationDestination modifier and move BackyardView into the detail view builder. Since selectedBackyard is an optional, let's make sure to unwrap it and provide a fallback view.
Finally, let's use a list initializer that takes a selection binding. When you provide a value for the selection parameter of a list, the selection binding drives which detail view we present within our NavigationSplitView.
Nice. We now see the detail view for our selected yard by default. Since we launch directly to the detail, we can remove the navigationTitle. We can navigate back to see the rest of our backyards using the source list button. And tapping back into the detail view gives us a beautiful full-screen animation. Right now, the backyard view is a long list of scrollable content. Our list is already broken up into three different sections with distinct sets of functionality, the today view, the habitat view and the visitors view. We'll be able to improve the visual clarity of our app by instead using a TabView and creating tabs for each section.
TabView gives us a way to break up content into full-screen views, with each tab having a clear and distinct purpose. I want to keep Digital Crown functionality for quick scrolling through views. The new vertical page style lets me do this with TabView. People can quickly navigate between tabs and even scroll within a single tab.
Let's jump back to Xcode and update the detail view to use a TabView.
In BackyardView, let's move each section into its own respective view and replace the List with a TabView. Since each page of the TabView takes up the full size of the screen, we don't need the sections and dividers. Let's make sure to add the section headers back in as navigationTitles.
Lastly, let's use the tabViewStyle modifier and specify that we want verticalPage. We're almost there. As we paginate, we see views settling into each tab. To make it easier for people to quickly view all of the visitors, we can wrap our VisitorView in a List.
Now, the TabView will designate one tab to its child List view. If the list exceeds the height of the screen, the view will become scrollable.
If you have scrollable content within a vertical TabView, place it in the last tab whenever possible.
I'm also going to break up the habitat view into two separate tabs, so that people can easily distinguish between food and water levels.
For this, I've created my own gauge that fits better within a TabView.
In our TabView, let's replace HabitatView with our new GaugeView by creating one instance for food and one for water.
That's looking great. Now all we need to do is add back the refill functionality. For this, I am going to use a toolbar. Toolbar has been elevated in watchOS 10 with brand-new placement options, consistent across all screen sizes. The new bottom bar placement is a great place to put controls. I'm going to make use of this placement option by adding in the Refill button to the bottom bar.
Back in the new HabitatGaugeView, we'll add a toolbar and then use a ToolbarGroup to specify we want bottom bar placement. I'll use a spacer to add the button to the trailing corner of the toolbar.
We now have a lot more space, and it's easier to focus on the Habitat summary. I want to do more to help orient people as they navigate through the TabView. Additionally, if the food and water of a backyard is low, I want to bring attention to this. We can achieve this by applying a background gradient. The title for each tab is helpful, but color would be a great way to increase glanceability and reinforce where we are within the detail view. We can also use it to highlight the state of our backyard if food or water is running low. The system gradient is a nice way to relay this while maintaining contrast against foreground elements. Let's use the container background modifier with a color gradient to achieve this. Let's first remove our original background. We can add a function to the BackyardView that determines the background color based on the current food and water level. If levels are low, the background will be red to indicate that it's time for a refill. I'll also add some helpful computed variables. Now we can pass this computed property to the containerBackground modifier.
The modifier takes in a ShapeStyle where we'll pass in the color gradient as well as a ContainerBackgroundPlacement. Here, we'll specify it's for a TabView.
I also want to change the gauge color when the background changes. Let's pass in the same computed color variable.
Let's apply the containerBackground modifier to the other views within the TabView, using the app's accent color.
As we scroll, you can see that the gradient contrasts nicely against the foreground elements. And as we tap on the toolbar button, we can see how the background gradient changes.
As a final touch, I want to make use of materials to highlight important information. Materials have been available across platforms and are new to watch apps in watchOS10.
They're a nice way to distinguish between foreground and background elements while also adding a visual flourish. Finally, they can surface or reinforce information on screen. There's a couple places in the app where we can use materials and vibrancy. Let's take a look. To start, I want to replace the shadow backdrop from the backyard title and instead use a material background. The background of the cell is colorful, and adding in a material will help provide clarity to the title.
People really care about the number of bird visitors in each backyard, which you can see in the summary view. It would be great to surface it in the list view, so that people can see it without going into the detail view of a backyard. I'll add it as another overlay and provide a material background to the number. watchOS apps tend to use dark backgrounds, so the material defaults to dark variants. Because the backyards are light and colorful, I'm going to switch these to the light variant.
And with that, our Backyard Birds app has now been updated for watchOS 10.
Let's review all the big changes we've just made. We've added in the NavigationSplitView to our list. We then replaced the List with a Vertical TabView. We added functionality to the toolbar with each backyard, as well as some beautiful gradients. To finish, we made use of materials to surface useful information. Thanks for coding along with me. This is a big year for Apple Watch, and I encourage you to explore more sessions. To go further with Backyard Birds, head over to "Build widgets for the Smart Stack on Apple Watch". I can't wait to see the updates you'll make so your app shines on watchOS 10.
-
-
4:02 - NavigationSplitView
NavigationSplitView { List(backyardsData.backyards, selection: $selectedBackyard) { backyard in BackyardCell(backyard: backyard) } .listStyle(.carousel) } detail: { if let selectedBackyard { BackyardView(backyard: selectedBackyard) } else { BackyardUnavailableView() } }
-
6:18 - Vertical TabView
TabView { TodayView() .navigationTitle("Today") HabitatGaugeView(level: $waterLevel, habitatType: .water, tintColor: .blue) .navigationTitle("Water") HabitatGaugeView(level: $foodLevel, habitatType: .food, tintColor: .green) .navigationTitle("Food") List { VisitorView() .navigationTitle("Visitors") } } .tabViewStyle(.verticalPage)
-
8:37 - Add refill button to Toolbar
.toolbar { ToolbarItemGroup(placement: .bottomBar) { Spacer() Button { level = Int(min(100, Double(level) + 5)) } label: { Label("Add", systemImage: "plus") } } }
-
9:48 - HabitatGaugeView background color function and variables
func backgroundColor(_ level: Int, for type: HabitatType) -> Color { let color: Color = type == .food ? .green : .blue return level < 40 ? .red : color } var waterColor: Color { backgroundColor(waterLevel, for: .water) } var foodColor: Color { backgroundColor(foodLevel, for: .food) }
-
10:10 - .containerBackground within TabView
TabView { TodayView() .navigationTitle("Today") .containerBackground(Color.accentColor.gradient, for: .tabView) HabitatGaugeView(level: $waterLevel, habitatType: .water, tintColor: waterColor) .navigationTitle("Water") .containerBackground(waterColor.gradient, for: .tabView) HabitatGaugeView(level: $foodLevel, habitatType: .food, tintColor: foodColor) .navigationTitle("Food") .containerBackground(foodColor.gradient, for: .tabView) List { VisitorView() .navigationTitle("Visitors") .containerBackground(Color.accentColor.gradient, for: .tabView) } } .tabViewStyle(.verticalPage) .environmentObject(backyard) .navigationTitle(backyard.displayName)
-
11:38 - Add material to the backyard name
.foregroundStyle(.secondary) .background(Material.ultraThin, in: RoundedRectangle(cornerRadius: 7))
-
12:15 - Visitor score overlay with materials
.overlay(alignment: .topTrailing) { Text("\(backyard.visitorScore)") .frame(width: 25, height: 25) .foregroundStyle(.secondary) .background(.ultraThinMaterial, in: .circle) .padding(.top, 5) }
-
12:20 - Light materials
.environment(\.colorScheme, .light)
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.