How to Get HealthKit Permissions in WatchKit
by
Lou Franco
Here is the main running screen for Sprint-o-Mat:
The upper-right shows how many miles you have run so far, and the bottom right shows your current heart rate. The rings in the center fill up based on your running distance.
It's not shown on the screen, but many people like to track the calories they have expended during a workout, so Sprint-o-Mat tracks and stores them with the workout in HealthKit when it's done.
The user's running distance, heart rate, and calories expended are only available if you get permission beforehand. You also need permission to save Workouts in HealthKit so that they will show up in the Fitness app or be readable by other apps.
Here's how you do that.
It's not just code
Apple wants to know that your app is going to request permissions, so you must register your intent to ask (and the reason) in your app's plist.
You need to add two keys to the Info.plist in your WatchKit Extension.
- Privacy - Health Share Usage Description: Set the value to a string that describes why you are asking to read HealthKit data.
- Privacy - Health Update Usage Description: Set the value to a string that describes why you are going to write data to HealthKit.
For Sprint-o-Mat, I set the Share string to "Sprint-o-Mat displays health information during workouts and uses it to step through the planned sprint." And I set the Update string to "Sprint-o-Mat stores completed workouts in the Health app."
These strings will be displayed on Apple's HealthKit permissions screens so that the user knows why they are being asked to share access to HealthKit.
IMPORTANT: If you don't set these values in your plist, your app will crash as soon you try to ask for permission to use HealthKit data.
Update your Privacy Policy
If you use HealthKit data, Apple also requires that you explain in detail what you are using and why in your privacy policy. They also want you to disclose any sharing that you might do with third parties.
This is a new policy that went into effect in 2020 with the privacy nutrition labels. I didn't realize this and got a rejection when they added it. But now my current privacy policy has been accepted, so you can look at Sprint-o-Mat's privacy policy to see what will probably be enough. I am not a lawyer, so you should not really be copying my privacy policy, but it will give you an idea of what you need for Apple to accept your app.
Check for permissions often
I'm going to show you the APIs to request permission and query for permission status. Even if you ask for and receive permission to read and write HealthKit data, this could be revoked at any time. So, do not try to keep track of whether the user gave permission. When you need to know, use the API again.
In Sprint-o-Mat, there is code on the main screen to check permissions every time it is shown. This makes sure I check on start-up and after a workout is done. The workout start button will also not be enabled if they don't have permission at that time.
I have found that even if I never revoke permissions, from time to time, the SDK will make me ask again, probably to make sure that the user still knows that the app accesses Health and to remind them of what exactly it uses.
Requesting permissions for workout apps
The first thing you need to do is ensure that HealthKit is even available on the device you are running on. This should be the case for all watches, but it's still good form to check in case your code ends up in another app or things change for some reason. Here's how you do that.
guard HKHealthStore.isHealthDataAvailable() else {
// Here you need to let your app/user know that HealthKit isn't on this device
return
}
What you do exactly in that guard depends on your app. In Sprint-o-Mat, most of the model functions publish Result
types and in this case, I send an result with an error. The view model and UI of the app know how to deal with that.
Once you know you have HealthKit on the device, you can do this:
let healthStore = HKHealthStore()
This object is your access to the functionality of HealthKit. I have a manager class that creates one of these in its init()
.
To request authorization, you need to prepare two lists. A list of quantities you intend to read from HealthKit and a list of samples you intend to store.
Here is what I read in Sprint-o-Mat:
let typesToRead = Set<HKQuantityType>([
HKQuantityType.quantityType(forIdentifier: .heartRate),
HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned),
HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning),
].compactMap { $0 })
It is possible for HKQuantityType.quantityType(forIdentifier:)
to return nil. This should never happen for the quantities I am asking for, but since you need to prepare a Set of non-nil quantities, I use compactMap
to remove the nils (this is mostly for the type-checker).
The specific quantities you need depend on the type of workout you are going to do. For example, there are different quantities for swimming, cycling, or wheelchair workouts. See the documentation for a full list.
Here is the list of things I write:
let typesToShare: Set = [
HKSampleType.workoutType(),
HKSeriesType.workoutRoute()
]
The first sample type is the workout itself, which includes the distance, paces, duration, and calories. If the user has also given permissions for me to track their location during a run, I will create a workout route and store that as well. We'll see how to do location permissions in the next article.
To see the full list of what you could store, check the documentation.
To request the authorization use this code:
healthStore.requestAuthorization(
toShare: self.typesToShare, read: self.typesToRead) { [weak self] (success, error) in
DispatchQueue.main.async { [weak self] in
if error != nil {
// Deal with errors
} else {
// Authorization succeeded
}
}
}
What you do specifically in your completion depends on your app. Note that success does not mean that the user gave you permission, only that the request for permission didn't run into some error.
Also note that once you ask for permission and the user doesn't give it, you aren't allowed to ask for it again. All you can do is let the user know that they have not given permission, and that they must use the settings app to grant the permission if they change their mind.
How to query for permission status
Before you ask for permission, you can check to see what would happen if you asked. You do that with this code.
healthStore.getRequestStatusForAuthorization(
toShare: self.typesToShare, read: self.typesToRead) { (status, error) in
if let error = error {
// There was an error in HealthKit
}
if status == HKAuthorizationRequestStatus.unnecessary {
// Nothing will happen if you ask for permission
// This does not mean you have permission
} else {
// You should ask for permission
}
}
Again, getting a result of HKAuthorizationRequestStatus.unnecessary
does not mean you have permission. It means that if you ask for permission, nothing will happen. This could be because you already asked and were denied.
To figure out the difference, you need to use more fine-grained methods.
Querying the status of each kind of permission you want
Once you request or check for authorization, and that request succeeds, you still don't know for sure that you have permission. You only know that there wasn't an error in making the request.
The user also had the option of allowing some data, but denying others, so the only way to know for sure what happened is to ask about each kind of sample you want to write.
To ask about workout samples, use this:
let isWorkoutAllowed = self.healthStore.authorizationStatus(for: .workoutType())
If you were denied saving workouts, then the result will be .sharingDenied
.
If you were denied permission to save workouts, you will not be allowed to ask for this permission again, so the only thing you can do is to direct the user to allow it manually by going to the Setitings app on the watch, tapping Health and then Apps and then looking for your app and adjusting the settings there.
Checking for read permission is a lot more complicated.
Apple considers it a privacy violation to let you know that the user denied access to something because that reveals that they had it in the first place. So, there is no way to tell if data you want to query is denied access or just non-existent.
So, you'll just have to deal with this in the workout itself. If you don't get any data, then it's probably true that you don't have permission (since the data we ask for is definitely available during workouts). You have to handle that case there—which I will cover when we build the code to run a workout.
Remember, even if you were granted access to all permissions, the user can use the Settings app to revoke the permission, so you need to check every time you plan to use HealthKit.
In the next article we'll look at getting permission to track the user's location during a workout.
Subscribe to be notified of the next WatchKit article
This article is part of a series covering Workout apps. If you want to see articles about WatchKit in general, see How to Develop Apple Watch Apps with WatchKit.
Make sure to sign up to my newsletter to get notified when a new article is published.
Contact me if you are working on a Watch app and need help.