WorkoutKit makes it easy to create, preview, and schedule planned workouts for the Workout app on Apple Watch. Learn how to build custom intervals, create alerts, and use the built-in preview UI to send your own workout routines to Apple Watch.
♪ ♪ Tu: Hi, my name is Tu Nguyen, and I'm an engineer on the Workout team. I'm here today with my colleague Abhiraj, and we'll walk you through how to build custom workouts for Apple Watch.
In watchOS 9, we introduced new workout types in the Workout app for users to create and customize their ideal workout experience: Goal based workouts where users can perform a workout with a singular goal, such as distance, energy, or time. Pacer workouts that puts the focus on pace or speed front and center.
Swim-bike-run workouts for triathletes to seamlessly transition between swim, bike, and run activities.
And custom workouts, structured steps with a combination of custom goals and alerts.
So in iOS 17 and watchOS 10, we're bringing all of these workout types into a new framework called WorkoutKit.
WorkoutKit is a brand-new Swift framework. It allows you to create and customize all of the different workout types that a user can create in the Workout app within your own apps. It is also the bridge to help you bring these workouts into the Workout app for users to perform. WorkoutKit provides preview UI and the ability to sync scheduled workouts.
We're bringing these four workout types to WorkoutKit, and today we're going to be focusing on custom workouts.
In this session, we'll be going over building a custom workout, previewing for export, and how to schedule workouts. Let's first jump into building a custom workout.
Custom workouts are a great way for users to focus their workout in a structured manner. They contain a series of distinct steps that guides the user through their workout. When looking at a custom workout, there are three distinct stages. First, we have a step at the beginning of the workout. This is the warmup step. Next, we have an ordered collection of repeatable blocks, which contain their own combination of steps. These blocks represent the majority of the workout. And finally, a step at the end of the workout that we refer to as the cooldown step. Now, whether it's a warmup, cooldown, or step within a repeatable block, every step contains two important attributes. First, every step contains a single goal. Goals define the progression of steps within a custom workout. When a goal is complete, the custom workout moves on to the next step.
A step within a custom workout can have a time or distance goal, if applicable. You can also set an open goal, which requires the user to manually progress through the step.
And secondly, every step can contain a single alert. Alerts inform the user of a particular metric that indicates their current performance. For example, the user may want to be alerted when their heartrate is elevated past a certain threshold.
And we're bringing support for pace, cadence, power, and heartrate alerts.
Now that we know what makes up a step, let's take a closer look at blocks.
Blocks contain steps distinguished as work steps or recovery steps. The steps within a block contain their own goal and alert, as you saw before, and you can have any number of steps in any order within your block. Blocks can also be repeatable. You can specify the number of iterations that you want your block to be repeated.
Now let's see how this translates into the in-workout experience on Apple Watch. First, we see that the user's current step is a work step within the block. Since this step has a distance goal, we see the current goal progress with 0.2 miles remaining. This step also has an alert for current power, which we display at the bottom. If you don't happen to specify an alert for this step, we'll display the user's current heartrate.
And finally, we also give a preview that the next step is a recovery step with a time based goal. With steps and blocks, you are able to construct a complete custom workout.
Again, at the very beginning, we have a warmup step, then a series of repeatable blocks containing work and recovery steps, where we spend the majority of our time in the workout, and finally, a cooldown step at the end.
Now I'll hand it over to my colleague Abhiraj to show you how to build your own custom workout with WorkoutKit.
Abhiraj: Hi I'm Abhiraj. I'm an engineer on the Workout team. As Tu mentioned, with WorkoutKit you can create workouts for goal, pacer, swim-bike-run, and custom workouts. Let's take an example of a custom outdoor cycling workout. We will have four steps for this workout: Warmup, two repeating blocks of work and recovery, and cooldown to end the workout. For our warmup step, we'll use an open goal. For our first block, we'll have a work step with a distance goal and pace alert and a recovery step with a distance goal and heart rate alert. This block will have four iterations. For our second block, we'll have a work step with a time goal and power alert and a recovery step with a time goal and heart rate alert. Finally, our cooldown step will have a time-based goal Let's see how we can represent this in code. Let's start with our warmup step. First, we import WorkoutKit. As we mentioned before, our warmup step has an open goal with no alert. So in this case, we'll create it with the default initializer. Let's move on to the first block of this workout. In this first block, we'll have two steps. The first step is a work step with a distance goal and pace alert.
We'll need to import HealthKit here since we're leveraging HKQuantity and HKUnit to represent our goals and alerts. We'll now set up a 2-mile goal using WorkoutGoal. We also add a pace alert to this step, aiming for a pace of 10 miles per hour. Let's first create HKQuantity of pace value with 10 miles per hour. We'll use WorkoutAlert for creating this alert. To create WorkoutAlert, we'll need WorkoutAlertTargetType, in this case, target with 10 miles per hour, and a WorkoutAlertType, in this case, current pace. And now we can create our pace WorkoutAlert. Now, to create our work step, we'll use BlockStep of type work with twoMileGoal and paceAlert.
The 2nd step of this block is a recovery step. We'll create a 1/2 mile goal for recovery with a heart rate zone 1 alert.
Then we can create our recovery BlockStep of type rest.
Now, we can create our block with the work and recovery steps we just defined. To do this, we use IntervalBlock with an array of steps and iterations set to 4.
And with that, our first block is complete. Now lets move on to our second block. In this block, we also have two steps. The first step is a work step with a time goal and power alert.
We set up a two-minute goal. For this work block, lets create power range alert with a range of 250 to 275 watts. We create our WorkoutTargetType of type range and create WorkoutAlert. Finally, creating BlockStep using the goal and alert that we just created.
The second step of this block is a recovery step. We create a 30 second goal with a heart rate zone 1 alert. Then we can create our recovery step.
Now we can create our second block with the work and recovery steps we just defined with iterations set to 2.
Finally, let's move on to our cooldown step. We wanted our cooldown step to have a time-based goal, so let's create that here.
We create WorkoutGoal of type time and use that to create CooldownStep with goal set to fiveMinuteGoal.
Now let's put it all together.
Using everything we've built so far, warmup step, block1, block2, and cooldown step, we can create our custom workout composition. We set the activity type to cycling and location to be outdoor. LocationType parameter of CustomWorkoutComposition's initializer is optional, and by default, it's set to outdoor.
Lets combine all this and create our custom workout named "My Workout." You might also be wondering why initializer of CustomWorkoutComposition is preceded by a try. That's because we're validating the composition. Let's discuss the importance of validating workout compositions. Validations ensure a coherent workout structure and prevents any issues during workout runtime. For instance, distance goals should not be used for non-distance based workout compositions. Similarly, pace alerts should not be applied to workouts that do not support pace, such as elliptical. To help with this, we have a series of validations that are performed when certain properties are set, example, workout activity type, or when running through certain API.
We have also created WorkoutComposition wrapper to perform additional operations. For example, we have also created APIs to import and export workout composition to a file which can be shared across devices. Use dataRepresentation to export in JSON or binary. We recommend exporting your compositions in binary format for its much smaller size.
And now I'll hand it over back to Tu to talk about how you can preview compositions.
Tu: Thanks a lot, Abhiraj. It's great to see how we can leverage these APIs to build fully custom workouts. Now let's take a look at what we can do once we have workout compositions.
With a workout composition, there are a few different things you can do with them in WorkoutKit. First, as Abhiraj mentioned earlier, you can export the composition to a file with a .workout extension for sharing and distribution. Remember that validation automatically happens when calling certain API, and exporting a composition is one such example.
We also have API for you to present a preview of the contents of your composition to the user. Now, this has different behavior on iOS and watchOS.
On iOS, when you call the preview API on your composition, an out of process UI will be presented on top of your app and display the entire contents of your workout composition. In this example, we have a custom workout composition.
The title is prominently displayed at the top, the list of steps and blocks, including the goals and alerts, and an option for users to save the workout directly into the Workout app on their Apple Watch. Now, if we turn to watchOS, calling the preview API will launch the Workout app with the contents of the workout composition. From here, the user is able to immediately start the workout or save it for later.
Again, I'll hand it off to Abhiraj to show you these preview options with WorkoutKit.
Abhiraj: Now that we have a great workout, let's get it saved by the user. As we discussed earlier, we can use WorkoutComposition to perform additional operations on the workout, such as validations. With the WorkoutComposition, we can present a preview to the user. The preview API is optimized for iOS and watchOS to provide a convenient way for users to preview, save, or start workouts. So lets take the cycling workout that we created earlier and wrap it in a WorkoutComposition.
You can use any workout type that Tu mentioned earlier with a WorkoutComposition. Now lets present the preview using the presentPreview function on workoutComposition.
We use a task block here because this API is designed to work with Swift's modern concurrency features.
As mentioned before, presentPreview will display the workout preview differently depending on the platform it's running on. Let's see how this works. On iOS, the function presents a remote view on top of your app. It shows the workout and an option for the user to save it directly to the Workout app on the Apple Watch. The "Add to Watch" button will update the Workout app with new workout.
The preview function behaves differently on Apple Watch. Rather than presenting a sheet over your app, calling the preview function on watchOS will launch the Workout app with a preview of the workout. And now I'll hand it back to Tu to talk about scheduling workouts. Tu: Thanks, Abhiraj. Those preview options are really helpful to get a single workout composition in front of the user for a quick interaction. But what if you have a collection of workouts for the user to perform over a period of time? For example, let's say you have some cycling scheduled for your user today, and later in the week, hiking, a few days after that, golfing, maybe some scheduled rest, before some more cycling. You could use the composition and preview APIs to save workouts directly into the Workout app, but now the user is responsible for managing all of these workouts and remembering when they need to complete them. It's not very scalable. To simplify this whole process, as part of WorkoutKit, your app can schedule workouts directly into the Workout app. Let's see what that looks like.
When you schedule workouts, your app will have a dedicated space at the top of the Workout app. This dedicated space will be styled with your app's icon and name and a preview of the next workout for the day. Tapping here will immediately start the displayed scheduled workout.
Tapping the ellipsis will show more details, including upcoming scheduled workouts that you synced over.
Scheduling workouts requires the user's permission, and syncing is handled locally. The user is able to see workouts scheduled in the next seven days and the previous seven days. You can sync up to 15 workouts at a time, and you can query for scheduled workouts that the user has completed from your app.
When querying scheduled workouts, they only contain the composition you created, the scheduled date, and whether or not the workout was completed by the user. They do not contain any health data. So if you want actual health statistics from a completed workout, refer to the HealthKit APIs.
And as part of WorkoutKit, we're providing an extension on HKWorkout to retrieve a workout composition, if it's available. Now I'll hand it over to Abhiraj to show you how to schedule workouts using WorkoutKit.
Abhiraj: Thanks, Tu. For apps to schedule workouts for the user, we are providing a set of APIs to support syncing workout compositions to Apple Watch for use within the Workout app. Let's actually build an app that takes advantage of the WorkoutKit APIs. We'll need to associate scheduled dates with workout compositions, and then sync them. Let me show you how to do it. I've started building an app that has placeholders to interact with WorkoutKit APIs. Let's see how I can leverage WorkoutKit to make this functional. Before we can start syncing workouts to the Workout app, we need to get permission from the user. You can call authorizationState on WorkoutPlan to check your app's current permissions. Since we haven't requested permission yet, we can request authorization from the user by calling requestAuthorization on WorkoutPlan.
I'm going to tap the "Request Authorization" button.
When calling requestAuthorization(), a user is prompted with an alert to opt into syncing. Users can also change this setting in Workout settings from the Watch app on iOS and the Settings app on watchOS.
Now that we have authorized our app, we'll get our current workout plan from the Workout app on Apple Watch. You can query WorkoutPlan using WorkoutPlan.current.
WorkoutPlan is our interface to store and modify scheduled workouts from our app.
I'm going to tap the "Get Workout Plan" button.
As we have not scheduled any workouts yet, the plan has no scheduled workouts.
Next, let's create and schedule some workouts. I'm thinking I want to go for golf today and cycling later this week. So lets create that schedule with some new workout compositions.
ScheduledWorkoutComposition object contains a workout composition, scheduled date, and a completion state. I have assigned today for the golfing scheduledDate and cycling scheduledDate for the day after tomorrow.
Let's see how that looks in my app.
As you can see, I have a golf workout scheduled for today and a cycling workout coming up in a couple of days.
Now that we have created our schedule, let's sync it to the Workout app.
Using the current workout plan, we'll append these new scheduled workouts to the scheduledCompositions array. Finally, to add these workouts to the Workout app, we call workoutPlan.save().
Let's see how this works in action.
Great. Now these workouts are synced.
As Tu mentioned, when you schedule a workout, your app will have a dedicated space at the top of the Workout app. Let's dig in to see what it looks like. Let's open the Workout app.
As you can see, my app's icon and name is present. Since I have a golf workout scheduled today, tapping here will start the workout. I can tap the ellipsis to see my upcoming schedule.
On the first screen, I see all of my workouts scheduled for today. If l tap "View More," I'll be able to see workouts that are scheduled for the next seven days and the previous seven days. Here I see both today's golf workout and my upcoming cycling workout that I synced earlier.
Now you have an understanding of how scheduled workouts are presented and how your app can have a dedicated space inside the Workout app. As you know, I've got a round of golf planned today. My tee time is soon.
So if you don't mind, I'll be right back.
I'm back. I had a great round of golf and shot 72 on the back nine. When the user completes a scheduled workout in the Workout app, the completion state will be updated. You can query WorkoutPlan using WorkoutPlan.current to determine which workouts were completed. If a workout was marked as completed in your app, you should update the completion state to make sure the user has the most recent information everywhere. Let's get back to our code.
In our sample app, we can check if scheduledComposition is completed. If it is, we are marking it as complete.
Let's see this in action.
It's a great practice to keep your app in sync with Workout app.
Now you have a basic understanding of how to implement the Workout schedule APIs, from requesting authorization to syncing compositions to Workout app. With that, I will hand it back to Tu. Tu: Thanks, Abhiraj, and nice to see you improving your golf game. So let's wrap up with some best practices to keep in mind as you're adopting WorkoutKit in your apps.
Consider which composition type best fits the workout experience that you want to display to the user. We saw that custom workouts are a very useful composition type, especially when a workout requires different levels of effort and attention with custom goals and alerts. However, swimming isn't supported with custom workouts. You would need to use a goal composition instead. Also, alerts are only available with custom workouts. If you don't need to specify your own alerts for something simple like a 30-minute run, consider using a goal or pacer composition instead.
Be sure to handle validation of your workout compositions. We provide very granular validation errors to help you pinpoint the source of any incompatibility or error. As a reminder, not all activity types support distance. Consider using a time or an open goal instead. And not all activity types will support certain alerts in a custom workout. Consider an alternative, such as a heartrate zone alert.
Keep your scheduled workouts up to date. Take advantage of any foreground or background runtime your app may have to do so. And finally, please send us your feedback. Try out the API. All of the sample code you saw today is available on developer.apple.com. Also, don't forget to check out the HealthKit session on synchronizing workout sessions. Thanks for watching. ♪ ♪
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.