watchOS 4 introduces numerous enhancements to the application lifecycle of an app on Apple Watch. This session will outline when your app has runtime, how much time it has, and how to maximize your opportunities to create a great experience on Apple Watch. Learn about background modes new to watchOS 4 and find out how taking snapshots with background app refresh have been simplified.
Hi, everyone. My name is Neil Desai, and I'm a WatchKit Frameworks Engineer, and I'm really excited to talk about the life of a watchOS app today.
So, watch apps have a phenomenal capacity to be simple yet incredibly powerful. Well-designed watch apps have the potential to be the ultimate companion on your user's wrist. Now, let's imagine your user for a second. What are they asking for when they want to use your watch app? Maybe they want to quickly glance at some information, or complete a simple task. We think about great watch apps that are designed to help your user accomplish their goals in a simple, quick way so that the technology disappears and the user can go about their day and have an enhanced experience, all because of your app. So, today we're going to design an app that I've been working on in watchOS 3. We're not ready to ship it just yet, but we'll get to take advantage of some of the new capabilities and enhancements in watchOS 4 to take our app to the next level. Today, we're going to talk through four different additions in watchOS 4 that can help design our app. We're going to talk about some new things, such as our new Unified Process Runtime. We're going to get some great performance improvements in our app, and I'm going to show you how to do so in watchOS 4.
We're going to talk through our new Frontmost App State. We're going to take advantage of this state and build an amazing experience for our user.
Also, we're going to talk about some new improvements we've made to Background App Refresh, and how we can take advantage of them.
And lastly, we're going to talk about some new background modes that are available in watchOS 4, and take advantage of one of them in our app.
So, let's get started and dive right into our new Unified Process Runtime. So, we've designed a single-process architecture that will help your apps become even faster and more responsive to your user. So, let's now just revisit some of the history of where we came from and where we're now at, today.
So in watchOS 3, your apps UI lived in the app process, which we managed for you, and your code lived in the watch extension, so we had two separate processes, in this case, that lived on the system.
So, we changed things internally and unified our process runtime, so now the UI and your extension code all get executed from the same process. And now, you might be wondering, why exactly did we do this? So ultimately, we wanted to improve the performance of all WatchKit apps on watchOS 4, so users could have an even better experience with your apps. And, I'm really happy to say, we've accomplished our goals.
We can see touch latency improvements of almost up to twice as fast in some cases. So, from the moment a user actually touches the screen, to when your code gets called into, we've seen a dramatic improvement.
Also, for example, we've seen dramatic frames-per-second improvements when tied to general pan gestures, and we expect launch performance to also improve.
And now, since we just have one process to manage your UI and your extension code, we now have upped the memory limits to account for this.
And, you might be wondering, there's a lot of architecture that we did internally, so what do you all have to do to change your apps to adopt this new mechanism? And, the great thing is that there are actually no changes required at all. So, you'll be happy to hear that all existing native WatchKit extensions in the App Store today will get this behavior as soon as your user upgrades to watchOS 4. This new Unified Process Runtime is completely backwards compatible.
As long as your app is built with watchOS 2 or later, you don't have to anything else. So, this means the app we're going to improve today doesn't need to change in Xcode, or on the App Store. It just works.
So now, let's talk about our new Frontmost App State.
On the watch, a user many times uses an app in a matter of seconds, and then they put their wrist down and expect the app to finish up whatever task needs to be finished.
Many times, a user wants to raise their wrist again and see your app has finished doing whatever it needs to, and they want it to feel like magic. And, users shouldn't have to keep their wrist up, for your app to have to finish doing whatever task it needs to finish, and this is a really common pain point for our users. So, we thought a lot about all the different ways we could give you all more runtime to help finish your tasks for your user, and today, we're introducing a collection of new capabilities we call the Frontmost App State to help you design experiences for this case.
So, without further ado, let's talk about our app. So, the app is called Apple Pie Me, and let's see how it looks. So, it's a food delivery service where you can get apple pies delivered wherever you are, so an apple pie a day keeps the doctor away, right? I think so, at least, I really hope so, because I eat an apple pie every single day, and they're delicious.
And, I'm pretty busy, and I always just want to quickly order my apple pie, and get it delivered wherever I am.
And, most importantly, I want to tap the order button and then simply put my wrist down and go about my day. In watchOS 3, it was a bit difficult to build a perfect experience, but with watchOS 4, I'm going to show you how to surprise and delight your users using our new Frontmost App State.
So, to help explain the Frontmost App State, we're going to talk through what exactly application state means on the watch, what does the term frontmost mean, the enhanced capabilities that you're going to receive, and lastly, we're going to walk through architecting our Apple Pie Me app.
So, let's say our user decides to launch our app. So here, the app is foreground, the screen is on, and the application state, in this case, is active.
So now, let's say our user decided to go back to the watch face. So, in this case, the app is actually in the background and, for instance, your application might be running for a complication update, for example.
And now, let's say our user decides to go back to our app. So, we're active in foreground again, and now the user's using the app, but then they decide they want to put their wrist down. So, the screen turns off, and your app is no longer foreground because of this. However, since your app was the last thing used, it's the frontmost app, so when the user raises their wrist again, your app will be what they see, instead of the watch face.
During this time, we think of your app as the frontmost app.
So now, when your app is frontmost in this way, the application state will be background, and you might be wondering how exactly, or how long does the app stay frontmost for? So, it's going to be two minutes for all apps. However, if there is an expectation by your user to see your app next when they raise their wrist, then you can use a new API to extend this timeout to be eight minutes.
So, for example, if you're a ride-sharing app in the middle of a ride, then you might want additional time in the frontmost state, because there's a reasonable expectation that the user wants to see your app the next time they raise their wrist.
However, in many cases, if you forget to turn this off when it's not needed, you can unwittingly annoy your user and leave them with a subpar experience. So, always just make sure to turn it off, to reset the time back to two minutes when you no longer need this experience.
So, let's jump into code to see how we might enable the extended timeout.
So, it's really simple. It's a Boolean property on our WKExtension, and when we want eight minutes, we can set it to be true, and when we want to go back to the default of two minutes, we just set it to be false.
So, there's one other point I wanted to mention. If the user raises their wrist and views your app, then the timeout is reset, regardless if, if you use the frontmost extended API or not.
Essentially, if the user is still interested in using your app, your app will stay frontmost.
So, now we know what frontmost means, but what does this all really mean? Like, what exactly does this new state give us? Well, we're going to get some enhanced capabilities. So, we're going to change the notion of how we get our WatchConnectivity resumes, and our NSURLSession resumes. So, our transfer and connectivity is now improved. We'll also change a little bit about our task completions. And then, we're also going to allow haptics to be played in this frontmost state. And lastly, we're going to get a frontmost notification, so we can handle this via the user notifications framework, and choose the right experience.
So, let's dive right in. So, for WatchConnectivity, we've changed the way our background transfers work, so when you use on the phone, update application context, transfer user info, or transfer file, the request, when you send it from the phone to the watch, it'll just go right through, wake up your app on the watch, and then just deliver your payload.
And, there's one point I wanted to mention in relation to WatchConnectivity, is that send message won't work when you're frontmost, but the screen's off, because your application state is background. So, just always make sure that call is reachable first, and then that can allow you to send the message.
And, along the same vein as WatchConnectivity, we're also going to change the notion of our resumes for NSURLSession. So, when we have a background download, for instance, and the system's downloading it for us, and then when the system is finished, it's going to automatically wake up our app, if we're in the frontmost state, and deliver the data right to our application.
And, of course, with NSURLSession, there is actually two major parts. There is the resume, when the system has actually completed downloading whatever it is you need, but then there's also the initiation of a download in the background.
So, in most cases, this will occur immediately, also, and in very rare cases, it could get delayed up to 10 minutes, but it's a very rare situation. If you've ever used NSURLSession, which probably almost everyone has here, for a background transfer, this is a really big deal on the frontmost state. To reliably depend on the initiation and the resume of our connectivity is really going to change the design landscape for the watch, and also, in addition, with our WatchConnectivity and getting requests right from our phone.
So, another thing is, a lot of times, a user might put their wrist down, and in that moment, your application goes into the background, but you might need to finish up a task. So, say you need to close a database, or do something else. So, you can just use the NSProcessInfo class to perform expiring activity API to get up to 30 seconds when foreground initiated, and up to 10 seconds when background initiated. So, for example, for background initiated, maybe you're running in a complication for an update, and you just need a little extra time to get done. So, you can just use the NSProcessInfo class to get some additional time.
And now, when you're frontmost, and you use that API, then you'll get increased priority by the system. So, because the system believes that your app is more important to the user, because it's still in the frontmost state, the priority by the system will get increased, so you're more likely to get the runtime that you need to finish up whatever task you need to do for your user.
And, another benefit of being frontmost is, you can now play haptics, so when you have runtime for another reason, you can easily just play a haptic.
So, sometimes haptics can get a little confusing here or there, so let me just dive in and talk to you about, so if haptics, if you play a haptic, and let's say some audio is playing out a speaker, then only the audio from a haptic will play, and it ducks what's currently playing. If audio is playing out of Bluetooth headphones and the screen is on, then the haptics will play, and if the screen is off, then just the audio will play, because that makes the most sense for your user.
And so lastly, the other benefit of being frontmost is, when you receive a remote or local notification via the user notifications framework, you'll then get called into, and then you can decide the right experience for your user. So, maybe you want to just update your UI, and play the notification like normally, you can have that choice, now.
And so, you'll get called into, when you just override the function userNotification willPresent notification withCompletionHandler. And, the key of all these benefits, and this can really change the design landscape, but the best part about all this is it's free, so if your app gets a WatchConnectivity or NSURLSession resume, your app will now get additional runtime. If your app implements the willPresent notification callback, you'll now get that for free in the frontmost state. Also, if, for example, you want to use a NSProcessInfo performExpiringActivity API to complete a task on applicationWillEnterBackground, you'll get some additional priority by the system.
You can change nothing in watchOS 4, and your current, native application will automatically be more glanceable and actionable by default. And, with the Unified Process Runtime we talked about earlier, your application will be even more responsive.
But, we want you all to take it a step further. Your apps can now leap forward in functionality if you take these new behaviors into account. You need to architect your apps for this experience.
So now, let's talk through the Apple Pie Me app, and get started architecting our app using these new capabilities. So, let's revisit some of the design goals for the app from the perspective of our user.
So, here we are at a timeline of our user, and really, the user just wants to order apple pie, and then they want to eat apple pie, and sometimes in between, they just want to view the status of their order. So, it's a pretty simple app from the user's perspective. But now, let's break down the problem from our app's perspective.
So, when that Order button is pressed, then we're going to send that order to our server, and eventually, when the restaurant has received the request properly, and we know that the pie is cooking, we're going to send a push notification. And then, we also want to tell our user when a courier is on the way, and when a courier is outside.
So, let's break this down some more, and take it step by step. So, we're at the Receive Order, and how exactly do we do this? Once the user taps the Order button, what exactly should we show? We're going to be sending the order to our server here, and can't guarantee it'll come back immediately, no matter what. And, do we show something like this, maybe a Loading indicator? So, sometimes Loading indicators do make sense to your user, but in this case, it's actually pretty confusing. I might see this, and I might think, "Oh, I need to hold my wrist up. How long do I have to hold my wrist up?" This isn't really a great experience on the watch. But instead, we could actually show something like this. So, hey, we'll tap you when the pie's ready. So, the user knows they can just drop their wrist and go about their day. So, now we know at the Receive Order screen, we know we just want to show this new UI immediately, so now let's break this down into its separate components.
So, we want to use NSURLSession and post the order to our server, and we also want to extend the timeout, and then just update our UI.
And lastly, there are times when our server might not be responsive, or something could have gone wrong, and we need to handle that case. So, we need a mechanism that allows us to schedule something around, let's say, five minutes from now. And, we also need something that doesn't depend on the network, or something that, regardless of frontmost or not, should alert our user.
We also need something that we can cancel later on, and so, if you're thinking about a local notification, then you'd be absolutely right. So, we could just show a local notification that says, "Oh no! Something went wrong." And then, the user can easily just tap on the Reorder button and get another apple pie, or they could just tap on the App icon and go into the app and see what went wrong.
So, let's just add that last step, and now, let's just jump into some code and see how this can be done. So, here we are at our IBAction for when the Order button is actually pressed, and then we just want to grab our background session, and then we're going to want to post the order to our server. And then, because there's a reasonable expectation that the user wants to see our app the next time, because they just pressed the Order button, then we're just going to extend our frontmost timeout. And then, we're going to trigger that just-in-case scenario, our fallback local notification, and then we're just going to want to reload some new UI.
Great. So now, we've received the order, and eventually we're going to send a push notification for when the pie is cooking. So now, let's say our user puts their wrist down sometime after we've received the order.
So, right before we send that notification, we expect to be in the Frontmost App State, but really, we, the developer, we don't really need to know too much about when the user puts their wrist down, or raises it up. The next event that's really just important is our push notification, to just alert our user that their pie is cooking. If the app is frontmost, we'll get our willPresent notification callback we talked about earlier.
Otherwise, if we're not frontmost, the user just gets a notification like normally.
And so, let's jump back into some code and see what we can do.
So, here we're just going to override our user notification center, willPresent notification withCompletionHandler. And then, the first thing we want to do is, we just want to make sure that we cancel our local notification. And then, we just want to reload our new UI, and then, because we did that, we want to just tap our user, so they know to raise their wrist. And lastly, we want to call the completionHandler and finish up. And, I wanted to point out for the completionHandler, it actually takes a UNNotification PresentationOptions, and so that's the options of alert, badge, or sound. And, in this particular case, we actually don't want a notification to appear on top of our app, because we've already tapped the user, and we've updated our UI, so instead, we're just calling completionHandler with none of the options, so essentially we're just going to be consuming the notification, and then, because the tap comes in, the user would raise their wrist.
So, we just tap the user, and they might see something such as the following.
So, great, their pie is coming. It's about 12 minutes away. It's cooking in the oven. So, here's where we were, and now let's go back to our timeline.
So, now we know our pie's cooking at the restaurant. And now, let's say we want the user to be able to raise their wrist and see as up to date of an ETA as possible. So, as soon as the user raises their wrist, we will, of course, ask for the most up-to-date ETA, and this means that the next time the user raises their wrist again, we'd expect them to see that up-to-date ETA.
Great. So, we handled our push notification. We now have a mechanism for updating our ETAs, and now we just want to send a push notification again for when the courier is on the way.
And, if we receive this push in willPresent, then we're just going to tap our user again, and then maybe we might want to add a map and an updated ETA showing either where we are, or where the courier is. And, let's say, after we sent a push notification for our courier is on the way, the user decides to maybe raise their wrist. So now, the user would see the map and where their courier is, and the app would provide a great, glanceable experience. And next, we can just send another push for when our courier is outside.
And then, if we're still in the frontmost state, we can easily just play another haptic like before, and update our UI to show that the courier is right outside. And, at this point, let's just make sure to disable our frontmost timeout extended API, since we expect our user to be eating apple pie now, instead of actually just using our app. So, let's take a look at our overall timeline.
So, here's our whole flow, and it all looks great, but let's try to challenge our assumptions as much as possible. Now, what happens if the user decides to go back to the watch face? So, we're no longer frontmost in that case, but does that really change anything? Not really, right? Let's take it through our app code again. So, we use the background NSURLSession to post the order to our server, so if, right afterwards, the user decided to go back to the watch face, we know that that will continue, and then properly post the order to our server. And then, we just send a push notification for all the different stages. So, even if we're not frontmost, the user just gets a regular notification.
And so, here we are at our overall timeline. So, we've handled the cases when our server wasn't working properly with the local notification, and we've worked through how to use some of the new Frontmost App State to provide a great experience to our user. And, the best part about all of this is that our app works great with or without the Frontmost App State.
And so, we've now gone through the Frontmost App State, and our Unified Process Runtime, and how it affects our app. So, let's just now take some time to revisit Background App Refresh, which was introduced last year, and see what improvements we've added, and how we can apply it to Apple Pie Me.
So, let's just go through a brief overview.
So, in watchOS, the system wakes your application by handing it a task, and the system has a limited number of these to hand out, so just make sure to make the best use of each one you get.
And, when the system wants to wake your app, it gives you one or more of these tasks, and then you can do any work you'd like in the background, and you just make sure to hold this task for as long as you're doing work. And, the way in which the system will deliver these tasks to you is when you implement your handle background tasks callback on your WKExtension delegate.
And so, when you finish your background work, you just return the task to the system by calling setTaskCompleted.
So, we've changed a little bit of the API, so let's walk through that now, now that we have a good, conceptual overview.
So, before, we would implement our handle background tasks, and then we would switch case on all of the different tasks. So, in this example, we have a WKApplicationRefresh BackgroundTask, and then, let's say we perform whatever functions we need to, to get our app up to date, and then we just want to schedule a snapshot.
And so, we use the scheduleSnapshotRefresh, and we use a preferred date. And then, we just call setTaskCompleted. And, the important thing here that was a little tricky was, it was really important to call setTaskCompleted after you actually scheduled a snapshot refresh, because if you called it before, then, like I mentioned, you're actually, by calling setTaskCompleted, you're returning the task to the system, and no snapshot would then be scheduled. So, this was a little bit cumbersome, and not the best experience.
So now, all you have to do is just call a new function that says setTaskCompletedWithSnapshot, and you set it to be true when you want that snapshot, and set it to be false when you don't.
And so, for all tasks, the default is just to take a snapshot immediately. However, for snapshot, just make sure to remember that there's a special call to setTaskCompleted with a snapshot. So, let's take a look at that. So, here it is, again, we're implementing our handle background task, and this time, we just have a WKSnapshotRefreshBackgroundTask, so we're just going to call setTaskCompleted with a restored default state, and then our estimatedSnapshotExpiration.
So, this is really great, and it's very simple to use, but I actually want to point out one cool trick. So now, you can actually use setTaskCompletedWithSnapshot on your snapshotTask. So, what that does is it will, of course, take a snapshot immediately, and then it'll actually schedule a snapshot an hour from now, if you set it to be true. So, essentially what this means, if you just keep calling setTaskCompletedWithSnapshot as true, you're going to set up a one-hour cadence, and it's really easy to constantly make sure that your app is up to date every hour.
And, if you want more custom data behavior, for example, then just make sure to call setTaskCompletedWithSnapshot false after scheduling your snapshot refresh, because if you call it with true in this case, then we're actually going to replace your previously scheduled snapshot, and just replace it with one an hour from now. So, if you do want that custom behavior, just make sure to set it to be false.
So, another important thing for our handle background tasks is how we actually update our complications. So, in watchOS 3, we actually soft deprecated on our CLKComplicationDataSource, our getNextRequestedUpdateDate, our requestedUpdateDidBegin, and our requestedUpdateBudgetExhausted. So now, in watchOS 3, I should say, if you implemented handle background tasks, these just wouldn't get called into, and now they're just fully deprecated, so they won't get called into at all.
So, if you want to update your complications, again, just make sure to handle background tasks, and then just call scheduleBackgroundRefresh with whatever date you want to update your complication.
And so, last year, a lot of times complications can be tied to some sort of networking task, and so last year, we guided you all to essentially, basically just schedule a background refresh, and then that way you would schedule it maybe for, let's say, 15 minutes from now, if you wanted a complication update by 7:45. So, essentially you would schedule that background refresh, and then you would get woken up around 7:30 to get that NSURLSession, and then you could trigger that, and then by 7:45, you would have enough time to update your complication.
But now, with some new NSURLSession APIs, we can actually just reduce a step here, and the reason we had these steps was really because we wanted to ensure we gave enough background running time to our NSURLSession. But now, with some new API in URLSessionTask, we can actually just set an earliest begin date. So, when we actually call the download task, we can actually just set on that task our variable, earliestBeginDate, so that way, we're telling the system, "Hey, we don't need this right now, we just need this at whenever date we've specified." And so now, we can actually just reduce that step. So, here in our schedule background refresh, we probably also just want to kick off our URLSession task, and set it up with the earliest begin date, and so that way the system, on our behalf, will actually just trigger that NSURLSession around 7:30 p.m., and then when that resume comes back, we can update our complication at 7:45 p.m.
So now, let's talk about how we can actually incorporate some of the Background App Refresh things we just talked about into our app, Apple Pie Me. So, here's that timeline we looked at earlier. So now, let's just focus in on the Pie's Cooking notification. So, if you remember, the code, before, it looked a lot like this. We implemented userNotificationCenter, willPresent notification with CompletionHandler, and then we just cancelled those fallback local notifications. We updated our UI. We then played a haptic, and called our completionHandler. And now, the great thing is, once you call that completionHandler, your app will get suspended, and you will get called into via handle background tasks with a snapshot task, and then your reason for that snapshot would be app backgrounded.
So now, when you have an ETA you can share, you can just make sure your snapshot looks as up to date as possible in the doc. And then, another thing I wanted to point out from before was, if you remember, we actually used, when the user raised their wrist, we then kicked off an NSURLSession to then grab the most up-to-date ETA. And so now, with that new API we just talked about on our URLSessionTask, we can actually just even schedule an ETA for some time from now. So, let's say, for example, maybe our pie's coming in 12 minutes, and we just want to schedule an ETA, like a refresh, around, let's say, like, eight minutes from now, for example.
So, let's think about our overall timeline once again. This was a really simple app, actually. It was just, we had an Order button, and then we were able to eat pie. And then, but, it was actually really powerful under the surface in our app code, and so we were able to take advantage of our NSURLSession resumes, our haptics in conjunction with our frontmost notifications. We're also taking advantage of our Background App Refresh, as well as we were able to schedule NSURLSession ETA requests. So, creating a great watch app that delivered you apple pies was certainly possible before, but now with watchOS 4, you can take a huge leap forward and wow your users.
So now, let's dive into some of our new background modes.
So, in watchOS 3, we introduced our workout session as a background mode, and now we actually have audio recording sessions, and then we also have navigation sessions. So, let's dive right into our audio recording session.
So, in watchOS 3, you could record audio, but it would, essentially the API would present a modal presentation that would show our UI, and then the user could then stop the recording, and then that file would get delivered to you all, the developer.
However, now you can actually just use your own UI, so you can build the exact recording experience you want in watchOS4.
And, the important thing is, now it can be background running but, of course, it's foreground initiated and background running, just like our workouts. And then, your app will stay frontmost while it's recording, and what's great about that is, you actually have the ability to play haptics. So, let's say, for example, you're an app that wants to detect what sort of song is playing around you. You could easily just press the Record button. The user would put their wrist down. You could detect what's playing, and then you could easily tap your user, so they know that, hey, you detected what song it was, and then you could show that to your user. And then, what's also great is that, just like workout sessions, it's a background running mode, but the heart rate sensor is not going to be on, so there's actually less battery life impact because of this. And, another thing I wanted to mention is, if for some reason the user decides to go back to the watch face, for instance, we're going to show that Microphone icon at the very top, so that way the user knows that something's recording, and then they can also tap on that icon and get launched right back to the app.
So, for playback, in watchOS3.1, we introduced AVAudioPlayer, and so now, for recording, all you need to use is AVAudioInputNode via AVAudioEngine, or AVAudioRecorder, and then our AVAudio recording for permissions.
And, there's a whole bunch of formats that are now supported. And so, let's talk about some of our locations background mode. So, just like audio recording and our workout sessions, it's foreground initiated, and then just background running.
And, of course, you're going to be frontmost while in session.
So, one thing to note is, you must call startUpdatingLocation while your app is in the foreground, and you should also just make sure to set allowsBackgroundLocationUpdates to true only when the navigation session has actually begun, and then set it to be false when it's not. So, if you recall, for Apple Pie Me app, we actually had two users. We had myself, the person that was about to eat that awesome apple pie, and then we actually had a courier that was going to bring it to us. So, in this case, we might actually just want to build a companion driver app for that driver, so that way they can easily be navigated on their wrist to our destination. And, the great thing is, you can even play haptics while in session, so now if, for example, for a right turn or a left turn, you could play a different haptic, or maybe when a user has missed a turn, for example. And, I'm really excited about this one, because, like, for example, transit apps that, you could imagine, you want to, just right before the stop you're about to get off at, the user could be easily tapped, and then they could know, oh, I need to go to the doors, and so I can get off at the next stop. So, we're really excited about our new Unified Process Runtime, our new enhancements to Background App Refresh, and our new background modes, and I especially am really excited about the new Frontmost App State. And, we think the design landscape will change in a really unique way on watchOS.
So, I challenge you all to design apps for the new Frontmost App State.
Think through the enhanced capabilities, and design experiences where the interactions are just two seconds. And so, we went through how to use NSURLSession, haptics, and frontmost notifications, but that only really scratches the surface. With the enhancements to watch connectivity and task completions, you could design even more powerful interactions that are incredibly simple to use. And so, I'm really excited to see how you all take advantage of these new capabilities. So, if you want more information about this session or any other session, feel free to go to developer.apple.com, and then we have some great related sessions. There's a great one tomorrow for planning a great Apple Watch experience. I highly recommend checking it out. I'm super excited for that one. And, I hope you all have a great WWDC. Thank you. [ Applause ]
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.