In the previous installment of HealthKit Fundamentals, we discussed the sensitivity of health data and the limitations of the HealthKit APIs with respect to accessing the user's health data. Apple put these limitations in place to ensure developers respect the company's commitment to protecting the customer's privacy.
We created a basic application in the previous tutorial in which we request the user's authorization for accessing their health data. More specifically, the application asks the user read and write access to their workout data. But how does the application know whether the user granted access to their health data? And if an application requests access to various types of health data, how can it find out which type of health data it can access and which it was denied access to? The answer is somewhat complicated.
Write Permissions
To request the authorization status of a particular type of health data, we invoke authorizationStatusForType(_:)
on an HKHealthStore
instance. This method returns an instance of HKAuthorizationStatus
, an enumeration. The member values of HKAuthorizationStatus
are:
NotDetermined
SharingDenied
SharingAuthorized
Note that the member values only refer to sharing or writing health data. Add the following snippet to the viewDidLoad()
method of the ViewController
class of the project we created in the previous tutorial.
override func viewDidLoad() {
super.viewDidLoad()
let authorizationStatus = healthStore.authorizationStatus(for: HKSampleType.workoutType())
switch authorizationStatus {
case .sharingAuthorized: print("sharing authorized")
case .sharingDenied: print("sharing denied")
default: print("not determined")
}
// Show/Hide Button
// enableHealthKitButton.hidden = !HKHealthStore.isHealthDataAvailable()
}
If the user granted write access to their workout data, authorizationStatus
is equal to .sharingAuthorized
. The value of authorizationStatus
is equal to .sharingDenied
if the user explicitly denied access to their workout data.
Understand that the user can modify your application's permissions at any time in the Settings application. We discussed this in the previous tutorial.
If your application hasn't requested access to a particular type of health data, the authorization status for that particular type is equal to notDetermined
. Update viewDidLoad()
as shown below. We replace the argument of authorizationStatus(for:)
with an HKQuantityType
instance for the user's heart rate. Run the application and inspect the output in the console.
override func viewDidLoad() {
super.viewDidLoad()
if let type = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) {
let authorizationStatus = healthStore.authorizationStatus(for: type)
switch authorizationStatus {
case .sharingAuthorized: print("sharing authorized")
case .sharingDenied: print("sharing denied")
default: print("not determined")
}
}
// Show/Hide Button
enableHealthKitButton.isHidden = !HKHealthStore.isHealthDataAvailable()
}
Did you expect the value of authorizationStatus
to be sharingDenied
? Since we haven't requested access to the user's heart rate data, the value of authorizationStatus
is set to notDetermined
. This tells your application that it didn't request access to the user's heart rate data yet.
Read Permissions
Throughout this series, I have emphasized the sensitivity of the user's health data. Earlier in this tutorial, I pointed out that the member values of the HKAuthorizationStatus
enumeration only contain references to sharing or writing health data. Why is that?
Apple considers the read permissions a user grants to an application in itself sensitive data. Assume the user doesn't want your application to read its heart rate data. That subtle piece of information could indicate that the user suffers from a heart condition. Even though this limitation of authorizationStatus(for:)
is unfortunate from a developer's perspective, I can appreciate Apple's reasoning.
How do you work around this restriction? The solution is simple but not perfect. If you request access to the user's workout data and the user hasn't granted your application access to that type of health data, HealthKit returns either no data or the workout data your application previously wrote to the HealthKit database.
From your application's perspective, there is no problem because the application is unaware that the user has denied access to their workout data. That said, if the user forgot she denied access to her workout data, she may be confused when she doesn't see the workout data of other applications. Unfortunately, that is a problem your application cannot solve.
Why does HealthKit return your application's workout data even if the user denied access to that type of health data? That question has a particularly interesting answer. If your application knows it wrote workout data to the HealthKit database and receives no results when it queries the HealthKit database for that type of data, it can infer, or at least assume, the user has denied read access to workouts. In other words, HealthKit is forced to return the health data the application wrote to the HealthKit database to camouflage the read permissions the user assigned your application. That makes sense. Right?
Updating the Application
I would like to end this tutorial by updating the application based on what we learned about permissions. If we haven't asked the user permission to their workout data, we show the Enable HealthKit button, otherwise, we hide it.
Has the user denied write access for workouts, then we show a message that informs her the application won't be able to write workouts to the HealthKit database.
Let us start by declaring an outlet for a label to the ViewController
class.
import UIKit
import HealthKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet var messageLabel: UILabel!
@IBOutlet var enableHealthKitButton: UIButton!
// MARK: -
lazy var healthStore = HKHealthStore()
...
}
Open Main.storyboard, add a label to the View Controller Scene, and wire it up to the outlet we declared in the ViewController
class.
In viewDidLoad()
, we update the user interface and populate the message label if necessary.
override func viewDidLoad() {
super.viewDidLoad()
messageLabel.isHidden = true
enableHealthKitButton.isHidden = true
if HKHealthStore.isHealthDataAvailable() {
let authorizationStatus = healthStore.authorizationStatus(for: .workoutType())
if authorizationStatus == .notDetermined {
enableHealthKitButton.isHidden = false
} else if authorizationStatus == .sharingDenied {
messageLabel.isHidden = false
messageLabel.text = "Meditations doesn't have access to your workout data. You can enable access in the Settings application."
}
} else {
messageLabel.isHidden = false
messageLabel.text = "HealthKit is not available on this device."
}
}
Run the application on an iPhone or iPad to verify that everything is working as it should. Change the application's permissions in the Settings application to see the results. Note that the user interface only reflects the application's permissions when it is launched.
What's Next?
In the next tutorial, we start working with HealthKit data. You learn how to read and write health data using a number of powerful APIs of the HealthKit framework.
Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files of the tutorial from GitHub.