I have a third-party app that is attempting to gather clinical health records from the iOS Health App. I have successfully followed these steps to gain access to apple healths 7 record types. For the requirements of this task, I also need to gain access to "Patient Data". This data can be found within the Apple Health App under accounts:
In the "FHIR Patient Data" tab:
Does the apple health kit API allow for this type of data fetch?
The answer to your question is yes, the API supports retrieval of FHIR records. You will have to pull the data apart yourself after you get it. Here is some example code which will get the records;
func getClinicalDocuments()
{
guard let cdaType = HKObjectType.documentType(forIdentifier: .CDA) else {
print("unable to create CDA type.")
return
}
allDocuments.removeAll(keepingCapacity: true)
let cdaQuery = HKDocumentQuery(documentType: cdaType, predicate: nil, limit: HKObjectQueryNoLimit, sortDescriptors: nil, includeDocumentData: true)
{ (query, results, done, error) in
if results != nil
{
for d in results!
{
self.allDocuments.append(d as! HKCDADocumentSample)
}
}
}
healthStore.execute(cdaQuery)
}
Related
I'm trying to get the underlying heart beat data from healthStore based on this conference at the WWDC 2019 - Exploring new Data Representations in HealthKit. At mark 24:40 is explained how HealthKit has a sensor capable of storing the exact timestamps of the beats and this data is accessible through the HKHeartbeatSeriesSample type.
I've created this query to retrieve that data:
func queryData(start: Date, end: Date) {
let predicate = HKQuery.predicateForSamples(withStart: start, end: end, options: [])
let heartBeatSeriesSample = HKSampleQuery(sampleType: HKSeriesType.heartbeat(), predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) {
(query, results, error) in
guard let samples = results, let sample = samples.first as? HKHeartbeatSeriesSample else {
print("Failed: no samples collected")
return
}
print(results as Any)
let heartBeatSeriesQuery = HKHeartbeatSeriesQuery(heartbeatSeries: sample) {
(query, timeSinceSeriesStart, precededByGap, done, error) in
guard error == nil else {
print("Failed querying the raw heartbeat data: \(String(describing: error))")
return
}
print(timeSinceSeriesStart)
}
self.healthStore?.execute(heartBeatSeriesQuery)
}
healthStore?.execute(heartBeatSeriesSample)
}
But it only works if I use no predicate, which then shows all heartbeat data stored previously, but during the time period nothing is stored.
I've managed to store some data using the HKHeartbeatSeriesBuilder the following way:
let builder = HKHeartbeatSeriesBuilder(
healthStore: self.healthStore!,
device: .local(),
start: start
)
builder.addHeartbeatWithTimeInterval(sinceSeriesStartDate: 0.5, precededByGap: false) {
(success, error) in
guard success else {
fatalError("Could not add heartbeat: \(String(describing: error))")
}
}
It stored the data and not the actual beats read by the Apple Watch device. Since I would need the actual beats to store them this way, then there must be a query that gives me that data before I have to store it in the first place (classic chicken and the egg).
How can I read the exact heart beats with timestamps during a given period of time?
Yes. Unfortunately it is not possible to extract the raw data with the apple watch alone. I opened a ticket in apple developer support and they said the advertised features were possible, using a third party device. Very disappointing and for sure not chosing Apple for a serious project like this in the future.
It was OK in development,
but when I distributed my app by TestFlight, I have had this problem.
While I was checking some failures,
I have guessed that when a user who didn’t create the record try to modify it, can’t do that.
By the way, I can fetch all record values on Public Database. Only modification isn’t performed.
In the picture below, iCloud accounts are written in green areas. I predicted that these have to be same.
image: Metadata - CloudKit Dashboard
Now, users are trying to modify a record by the following code:
func modifyRecord() {
let publicDatabase = CKContainer.default().publicCloudDatabase
let predicate = NSPredicate(format: "accountID == %#", argumentArray: [myID!])
let query = CKQuery(recordType: "Accounts", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil, completionHandler: {(records, error) in
if let error = error {
print("error1: \(error)")
return
}
for record in records! {
/* ↓ New Value */
record["currentLocation"] = CLLocation(latitude: 40.689283, longitude: -74.044368)
publicDatabase.save(record, completionHandler: {(record, error) in
if let error = error {
print("error2: \(error)")
return
}
print("success!")
})
}
})
}
In development, I created and modified all records by myself, so I was not able to find this problem.
Versions
Xcode 11.6 / Swift 5
Summary
I guessed that it is necessary to create and modify record by same user ( = same iCloud Account ) in this code.
Then, could you please tell me how to modify the record created by other user?
In the first place, can I do that?
Thanks.
Looks like you have a permissions problem. In your cloudkit dashboard, go to: Schema > Security Roles
Then under 'Authenticated' (which means a user logged into Icloud)
you'll need to grant 'Write' permissions to the relevant record type. That should fix it!
Let me describe the basic flow that I am trying to implement:
User logs in
System retrieves list of user's connections using HTTP request to 3rd party API (could be in the 1000s). I'll call this list userConnections
System retrieves stored connections from my app's database (could be in the 100,000s). I'll call this list connections
System then checks to see if each userConnection exists in the connections list already and if not, saves it to database:
for userConnection in userConnections {
if connections.contains(userConnection) {
//do nothing
} else {
saveRecord(userConnection)
}
}
The problem with this is that when the first users log in, the app will try to make 1000 saveRecord calls in a second which the CloudKit server will not allow.
How can I implement this in a different way using CloudKit and Swift so that I keep it to an acceptable number of requests/second, like ~30 or 40?
For anyone wondering, this is how I ended up doing it. The comment by TroyT was correct that you can batch save your records. This answer includes bonus of queued batches:
let save1 = CKModifyRecordsOperation(recordsToSave: list1, recordIDsToDelete: nil)
let save2 = CKModifyRecordsOperation(recordsToSave: list2, recordIDsToDelete: nil)
save1.database = publicDB
save2.database = publicDB
save2.addDependency(save1)
let queue = NSOperationQueue()
queue.addOperations([save1, save2], waitUntilFinished: false)
save1.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if (error != nil){
//handle error
}else{
//data saved
}
}
I have an app for which I want to add the possibility to backup data to iCloud using CloudKit.
The "backup" part seems to work correctly (my records are in the private database, since they are ... well, private).
Now I would like to use CKSubscriptions to keep all my devices in sync with the same data.
I tried to implement a CKSubscription to monitor record creation / update / deletion based on a query (not based on zones).
func subscribe() {
let options = CKSubscriptionOptions.FiresOnRecordCreation |
CKSubscriptionOptions.FiresOnRecordDeletion |
CKSubscriptionOptions.FiresOnRecordUpdate
let predicate = NSPredicate(value: true) // get all the records for a given type
let subscription = CKSubscription(recordType: "Stocks",
predicate: predicate, subscriptionID: subscriptionID,
options: options)
subscription.notificationInfo = CKNotificationInfo()
subscription.notificationInfo.alertBody = ""
db.saveSubscription(subscription, completionHandler: {
subscription, error in
if (error != nil) {
println("error subscribing: \(error)")
} else {
println("subscribed!")
}
})
}
Until now, I haven't been able to trigger a notification to my device.
I know that you can create a subscription based on zones. Zones are in the private DB, so I suppose that CKSubscriptions could work in the private DB.
But I didn't want to implement zones (that I don't need otherwise).
Question: is there an issue with CKSubscriptions in the private DB based on a query ?
This should work. Non zone subscriptions (query based subscriptions) are supported on private databases. Did you add the code to receive notifications in your AppDelegate?
I want to update the birthday in Apple Health. But I don't know how.
This is my authorization func:
private func requestAuthorisationForHealthStore() {
let dataTypesToWrite = [
HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth),
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
]
let dataTypesToRead = [
HKCharacteristicType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth),
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
]
self.healthStore?.requestAuthorizationToShareTypes(NSSet(array: dataTypesToWrite),
readTypes: NSSet(array: dataTypesToRead), completion: {
(success, error) in
if success { println("User completed authorisation request.") }
else { println("The user cancelled the authorisation request. \(error)") }
})
}
For requesting the birthday I call my function:
func requestAgeAndUpdate() {
var error: NSError?
let dob = self.healthStore?.dateOfBirthWithError(&error)
if error != nil {
println("There was an error requesting the date of birth: \(error)")
return
}
self.ageLabel.text = "\(dob)"
}
But how I can change/update the birthday programmatically?
Thanks for help!
You cannot change these characteristics programatically. The user must enter this data via the Health App.
From the documentation
The HKCharacteristicType class is a concrete subclass of the
HKObjectType class. HealthKit uses characteristic types to represent
data that does not typically change over time. Unlike the other object
types, characteristic types cannot be used to create new HealthKit
objects. Instead, users must enter and edit their characteristic data
using the Health app. Characteristic types are used only when asking
for permission to read data from the HealthKit store.
HealthKit Framework Reference;
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/index.html#//apple_ref/doc/uid/TP40014707
HealthKit objects can be divided into two main groups: characteristics
and samples. Characteristic objects represent data that typically does
not change. This data includes the user’s birthdate, blood type, and
biological sex. Your application cannot save characteristic data. The
user must enter or modify this data using the Health app.