Streaming is available in most browsers,
and in the WWDC app.
What's new in HealthKit
Bring the latest HealthKit features to your health & fitness app. We'll show you how to capture more detailed sleep data through sleep stages, track swim-bike-run and interval workouts with the enhanced Workout API, and save vision prescriptions — including an image of the physical prescription — directly to HealthKit while preserving privacy.
♪ Mellow instrumental hip-hip music ♪ ♪ Hello and welcome to WWDC.
My name's Karim, and I'm a HealthKit engineer.
The HealthKit framework provides the foundation for building great health experiences, and Apple Watch comes packed with many health and safety features, that keep an eye on things for you.
One of the most popular is sleep tracking, and we are making updates to capture more detailed sleep data.
We also have other great new additions to HealthKit this year.
We enhanced our APIs to make it easier to query for data with Swift async.
We're improving workouts with a richer workout representation.
And we're adding support for saving vision prescriptions, including a digital copy of the physical prescription.
I'm excited to tell you more about all these updates and how your apps can leverage them.
Let's get started! Sleep is such an essential function that allows our bodies and minds to recharge, and I love using my Apple Watch to manage my sleep schedule and get insights that help me understand and improve my sleep.
This year we are making sleep tracking even better by introducing sleep stages.
Apple Watch will automatically track all the different sleep stages you go through when you're asleep, and this data will be accessible from the Health app and saved in HealthKit.
Of course, your app will be able to read and save sleep stages data.
Sleep data saved by Apple Watch or your app is represented in HealthKit by category samples with the identifier sleepAnalysis.
We will support three sleep stages: REM, core, and deep.
When saving sleep data to HealthKit, you should make sure to create one sample for each continuous period of time in a given sleep stage.
This is what the sleep sample value enum looks like.
We're adding three cases to represent sleep stages, starting with asleepCore, which corresponds to stages one and two of the scoring model from the American Academy of Sleep Medicine or AASM; asleepDeep, which corresponds to stage three of the AASM scoring model; and finally, asleepREM, which corresponds to the rapid eye movement stage.
Now that we added sleep stages, we are deprecating the asleep case in favor of asleepUnspecified, which indicates that the user is asleep but no sleep stage was specified.
With the updated sleep samples enum, it is now possible to save and read sleep stages data from HealthKit.
We added a new predicate to make it easy to read sleep samples for a given stage.
Let's say I want to read sleep samples in the REM stage.
First, I create a predicate using the new predicateForSamples method with asleepREM as value.
Now, I can start building my query.
I create a predicate for my query with the sleepAnalysis sample type and the sleep stage predicate.
And with this query predicate, I can now create my query.
Running the query will give me back an array of sleep samples in the REM stage.
However, if you're interested in reading samples for all sleep stages, including unspecified, it's important that you update your app to use the new .allAsleepValues when building your predicate.
If you haven't looked at HealthKit since last year, this shorter syntax for querying may look unfamiliar to you.
Since iOS 15.4, we have updated our query API to support Swift async.
Queries are an essential piece of HealthKit, and with Swift async support, they're easier to use with a more concise syntax.
Queries allow you to read various data from HealthKit, using predicates to filter the results, and also watch for new data as it comes in.
All queries are subclasses of HKQuery.
If I'm interested in knowing the total calories burned over a certain period of time, a great query to get these computed statistics is HKStatisticsCollectionQuery.
To get the initial results, you would set the initialResultsHandler closure of the query.
And if you're interested in watching for any updates, you would additionally set the statisticsUpdateHandler closure.
Once the query is started, these closures will be called with the results.
Thanks to Swift async, we are making this even simpler.
Each query now has a matching query descriptor, so HKStatisticsCollectionQuery becomes HKStatisticsCollectionQuery Descriptor.
You get the initial result by simply calling the async result(for:) method.
If, on the other hand, you want to get the initial result and also watch for updates, you call results(for:), which returns an AsyncSequence that you can loop through to read the results.
HealthKit is great for tracking workouts and relevant metrics such as burned calories.
To find out how many calories I burned this week, I can use the statistics collection query descriptor.
First, I create the query descriptor with a predicate to match calorie samples.
I use the cumulativeSum option because I want the total sum.
I'm interested in this week's data, so I use thisSunday as my anchorDate.
And finally, I want my calorie totals to be computed for a time interval of one week.
Once I created my query descriptor, all I need to do is call result(for:) with a healthStore object.
The returned statisticsCollection object gives me a snapshot of my current data.
But if I want live updates as my calories change, it's as simple as calling results(for:) and looping through the returned async sequence to read the results.
When I'm done watching for updates, I just break out of the loop and that will stop the query.
Now, let's talk about workouts.
HealthKit is a great place for saving workouts and all their related metrics whether you're going on a casual bike ride or pushing yourself to the limit at a race.
And oftentimes, workouts may contain more than one distinct activity.
You may be repeating the same exercise with interval training or participating in a triathlon race which comprises swimming, cycling, and running.
We are updating our workouts API in iOS 16 and watchOS 9 to make it possible to capture these types of workouts and the relevant statistics for each activity.
This is the timeline of a swim-bike-run workout I did recently.
I started with swimming, after which I took a little bit of time to get ready for the cycling portion of the workout, and finally running.
Each activity is represented by an HKWorkoutActivity object.
Each workout activity is created with its own workout configuration, which includes the activity type.
A workout activity holds a list of events that occurred during the activity.
And you will be able to read statistics for each activity, which is great for when you're only interested in analyzing what happened during a particular activity.
Going back to my timeline, my three activities are configured with their appropriate activity type.
Activities cannot overlap in time.
And because there may be a transition period between each activity, they are not required to be contiguous.
If I'm interested in analyzing what happened during transition periods, I can create an HKWorkoutActivity for each transition with the type transition.
All of these activities will be saved with the HKWorkout object under the workoutActivities property.
If you're using HKWorkoutBuilder to add workouts to HealthKit, adding an activity is as easy as creating an HKWorkoutActivity object with a workoutConfiguration, start and end date, and an optional metadata.
And then, you simply call addWorkoutActivity on the workout builder.
On Apple Watch, you can use a workout session to track a swim-bike-run workout, and the associated workout builder to save the workout in HealthKit.
Let's go back to my workout timeline.
To track this workout on Apple Watch, I need to set up a workout session and builder.
I start by creating a workout configuration with the type swimBikeRun.
Then I create an HKWorkoutSession using my configuration.
At the beginning of the workout I simply call startActivity on the session, and beginCollection on the associated workout builder.
Now that my session and builder are ready, I can add my first activity using the beginNewActivity method with a swimming workout configuration and a start date.
At the beginning of each activity, you should make sure to update the workout builder data source to only collect the data types you're interested in.
Because this is a swimming activity, I want swimming distance to be collected.
At the end of the activity, I call endCurrentActivity with the end date.
Because I'm interested in analyzing the transition period from swimming to cycling, I will start a new transition activity immediately after swimming has ended.
Again, because this is the beginning of an activity, I update the builder data source to disable the collection of swimming distance, since it's no longer relevant.
I end the transition activity right before cycling begins.
I can track the remaining activities of my workout the same way.
At the end of the workout, ending the session will also end any running activity.
Then I can finish the workout builder, which will save and return an HKWorkout object.
I can use the returned workout to read some of the associated metrics such as totalEnergyBurned and totalDistance, and display a summary of the workout in my app.
However, this small set of properties is no longer sufficient.
totalSwimmingStrokeCount is not relevant to all workouts, and some workouts may collect more metrics.
In order to make it easier to read metrics for all kinds of workouts, we are deprecating these properties in favor of a new method which returns statistics for a given quantity type.
As a reminder, this method is also available on HKWorkoutActivity, allowing you to focus on just what happened during an activity.
These statistics will be automatically computed from any samples collected with the workout, only when using HKWorkoutBuilder or HKLiveWorkoutBuilder.
Along with this richer workout representation, we also have a new set of predicates to help you query for only the workouts you're interested in when driving your analysis or visualization.
To give you an example, here's a list of my recent workouts with the average heart rate for each activity.
I would like to find my workouts with high-intensity activities where my average heart rate was above 150.
First, I start by creating a predicate using the new predicateForWorkoutActivities method, which will act on workout activities.
I want the average heart rate to be greater than 150 beats per minute.
Next, because I want to query for workouts, I wrap my heart rate predicate inside a workout predicate.
Then, I create a query using that predicate.
And I call the result(for: healthStore) method of my query descriptor to get the list of workouts that match my predicate.
And that's how I'm able to query for just the workouts I'm interested in.
One of my recent workouts consisted of four running intervals.
Using HKWorkoutActivity is a great way to capture these intervals.
You just need to make sure that all activities of an interval workout have the same activity type as the workout.
So for a running workout, all activities are configured with the .running type.
One benefit of using workout activities to track intervals is you can get statistics for each interval.
With these updates, workouts now include a more comprehensive picture of their activities and the context surrounding them.
In order to provide an even richer picture, we're introducing new running metrics that will be automatically collected on Apple Watch Series 6, SE, and newer -- metrics such as running stride length, or power in watts.
For swimming workouts, we're adding the SWOLF score.
It's defined as the number of strokes taken in a given length, and the time it took to swim that length.
This score will be calculated for each lap event and segment event for swimming workouts recorded on Apple Watch.
While these metrics enrich your workouts and allow you to get a better understanding of how you performed, another important metric that is recorded after a workout has ended is heart rate recovery.
It's an estimate of how quickly your heart rate lowers after exercise, and it can be used to understand how the heart recovers after stress, and reveal potential health problems.
With iOS 16, we are introducing a new Cardio Recovery data type.
It will be accessible from the Health app, and your apps will be able to read and save this data in HealthKit.
Heart rate recovery is a quantity type with the identifier .heartRateRecoveryOneMinute.
Additional context information about each heart rate recovery sample can be added as metadata.
In my recent swim-bike-run workout, I pushed myself to my limits, and then observed my heart rate's recovery rate.
It took me about three and a half hours to complete the workout.
I reached a maximum heart rate of 184 beats per minute when I was running.
And the minute following the workout, my heart rate dropped by 50 beats.
When using HKLiveWorkoutBuilder on Apple Watch, a heart rate recovery sample, along with its surrounding context, is automatically saved in HealthKit after a workout.
Otherwise, to save a heart rate recovery sample, I create a quantity sample with the .heartRateRecoveryOneMinute type.
My heart rate dropped by 50 beats the minute following the workout, so I set that as the quantity of my sample.
I also set a start and end date for the sample.
Then, I put the additional context information in the metadata dictionary, starting with the recovery test type.
Because my workout was an all-out effort, the test type is .maxExercise.
My workout was a swimBikeRun, so I set that as the activity type.
I can also add the workout duration using the HeartRateRecoveryActivity Duration key.
Finally, I add the maximum heart rate observed during the workout, which was 184 beats per minute.
With these updates, it is now easier than ever to track swim-bike-run, and interval workouts.
In addition, the introduction of new metrics provides a more comprehensive picture for your workouts and newer ways to evaluate progress over time.
While activity and fitness is something that impacts everyone there are other aspects of health that touch on many of us, such as vision.
In fact, according to the Vision Council of America, approximately 75 percent of adults in the United States rely on vision correction with prescription glasses or contact lenses.
These prescriptions, however, are easy to lose, and it's one more thing you need to have with you when ordering prescription glasses or contact lenses.
So let's put them in our phones.
Starting with iOS 16, your apps can now save glasses and contacts prescriptions in HealthKit.
A vision prescription is a sample with the visionPrescriptionType.
The sample's start date corresponds to the prescription's issue date, while the end date corresponds to the expiration date.
Optionally, a digital copy of the physical prescription can be attached to the sample.
A glasses or contacts prescription sample is a subclass of HKVisionPrescription.
For glasses, you use the HKGlassesPrescription subclass, and for contacts, the HKContactsPrescription subclass.
Each glasses prescription is created using two glasses lens specification objects; one for each eye.
And similarly, contacts prescriptions are created with two contacts lens specification objects.
Now, let's save my reading glasses prescription to HealthKit.
The first step is to create an HKGlassesLensSpecification for each eye.
Some parameters, such as vertex distance and prism, are optional.
I can create the right eye lens specification the same way.
Next, I create a glasses prescription sample with my right eye and left eye lens specifications.
This prescription is for my reading glasses, so I'm adding that in the description.
Then, I just call save on the healthStore with my prescription.
And with that, my prescription is now saved in HealthKit.
I also took a picture of it, which I'd like to attach to the sample I just saved.
A file attachment is represented by an HKAttachment object.
You use HKAttachmentStore to save and read file attachments.
Only static images or PDF files can be attached to prescriptions.
To attach the picture I took to my prescription sample, I start by creating an HKAttachmentStore object using a healthStore.
Then I call addAttachment(to:) with my prescription sample.
I set a name for the attachment.
Here, I'm attaching a PNG file.
And finally, pass the URL of the file.
The prescription I just attached contains a lot more information than just the lens specification -- sensitive information, like my full name and date of birth.
One of HealthKit's core principles is to protect your privacy and make sure that you are always in control of the data you share.
Because it can be easy to share more data than intended with prescriptions that include an attachment, we are introducing a new authorization model for prescriptions.
Read authorization is granted for each prescription object separately.
Users can select exactly which prescriptions they grant your app access to, and update their selection at any time.
We have a new API for requesting authorization for vision prescription objects.
Just like other data types, you can use queries to read prescriptions that your app has access to.
If you're only interested in certain prescriptions, you can use a predicate.
To request authorization, simply call the requestPerObjectRead Authorization method of the healthStore with the visionType.
Doing so will always display an authorization prompt in your app with a list of all the prescriptions that match your predicate.
Make sure you're asking for authorization in the appropriate context to ensure the best user experience.
These are just some of the new things we added to HealthKit to empower your apps to provide better health and fitness experiences.
We can't wait to see what you will build next.
Have a great WWDC, and if you have any questions, we'll be happy to help you in the Developer Forums.
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.