iOS : Developing for HealthKit, DOB, Height etc coming back as nil - ios

So, I have been tasked with learning HealthKit, seems fairly straight forward. Started building my own app, can get steps etc. However I can't get date of birth, sex, etc. They are always returned as nil
I then downloaded Apple's sample app Fit, again comes back with nothing.
I have authorised both apps and created a medical id in health I am based in the UK? is it a us only thing?
func readProfile() -> ( age:Int?, biologicalsex:HKBiologicalSexObject?, bloodtype:HKBloodTypeObject?)
{
var error:NSError?
var age:Int?
// 1. Request birthday and calculate age
if let birthDay = healthKitStore.dateOfBirthWithError(&error)
{
let today = NSDate()
let calendar = NSCalendar.currentCalendar()
let differenceComponents = NSCalendar.currentCalendar().components(.YearCalendarUnit, fromDate: birthDay, toDate: today, options: NSCalendarOptions(0) )
age = differenceComponents.year
}
if error != nil {
println("Error reading Birthday: \(error)")
}
// 2. Read biological sex
var biologicalSex:HKBiologicalSexObject? = healthKitStore.biologicalSexWithError(&error);
if error != nil {
println("Error reading Biological Sex: \(error)")
}
// 3. Read blood type
var bloodType:HKBloodTypeObject? = healthKitStore.bloodTypeWithError(&error);
if error != nil {
println("Error reading Blood Type: \(error)")
}
// 4. Return the information read in a tuple
return (age, biologicalSex, bloodType)
}

Configuring Medical ID does not populate values for any HealthKit types. Try setting values for each type using the Health Data tab in the Health app.

Related

HKUnit for HKBloodGlucose in HealthKit with Swift

My name is Shak. I'm an iOS developer.
Recently, I started learning HealthKit and after some progress I have a problem which I need some help. Here is my problem:
I've been trying to save blood glucose data to healthKit but I'm getting this error that "Cannot convert value of type 'HKUnit.Type' to expected argument type 'HKUnit'". Here is the code:
let bloodGlucoseQuantity = HKQuantity(unit: HKUnit, doubleValue: Double(bloodGlucose))
func saveBloodGlucoseSample(bloodGlucose: Int, date: Date) {
guard let bloodGlucoseType = HKQuantityType.quantityType(forIdentifier: .bloodGlucose) else {
fatalError("Blood glucose type is not longer available in HealthKit")
}
let bloodGlucoseQuantity = HKQuantity(unit: HKUnit, doubleValue: Double(bloodGlucose))
let bloodGlucoseSample = HKQuantitySample(type: bloodGlucoseType, quantity: bloodGlucoseQuantity, start: date, end: date)
HKStore?.save(bloodGlucoseSample) { success, error in
if let error = error {
print("Error saving blood glucose sample: \(error.localizedDescription)")
} else {
print("Successfully saved blood glucose sample")
}
}
}
If anyone has any experience with HealthKit and specifically with blood glucose type I'll be grateful if you can help me.
let bloodGlucoseQuantity = HKQuantity(unit: HKUnit, doubleValue: Double(bloodGlucose))
You need to use a proper HKUnit instance and not just pass the class. e.g. pass HKUnit(from: "mg/dL") to unit.
I have developed the same application in Xamarin.forms. We have to pass the actual HKUnit instance and not the direct class. In xamarin.forms I have passed
HKUnit.CreateMoleUnit(HKMetricPrefix.Milli,HKUnit.MolarMassBloodGlucose).UnitDividedBy(HKUnit.Liter)
you have to convert this code in swift and you are good to go.!

Get the latest (most recent) Step value from HealthKit (HKStatistics)

I want to get the very last (aka recent) value of a Health(Kit) App entry.
For example the step value from the latest HKSource entry.
I thought that would be working with the HKStatisticsOptions.discreteMostRecent attribute in the query building.
Current result
The code works fine, no error. My statistics has the correct start and end date (if only one value is available, start and end date is the same).
The problem is that the statistics.sources list is always nil like the statistics.mostRecentQuantity() returns also nil.
All not HKSource related sum operations sumQuantity() work without any problem.
Other Stackoverflow posts
I would try the idea with using a query with limit 1 and a sort description from this post, but I need to show other historical values in a detail view too. That's why I thought, that I could request just a date frame of quantities, use the latest one as my "last" and all others for my history table view.
Code
func requestMeasurements(completion: #escaping (HKStatistics?, AppError?) -> Void)
{
let healthStore = HKHealthStore()
// Check if HK is available.
guard HKHealthStore.isHealthDataAvailable() else
{
completion(nil, AppError.healthInformationNotAvailable)
return
}
// Check if HK information is available
guard let quantitiyType = HKQuantityType.quantityType(forIdentifier: .stepCount) else
{
completion(nil, AppError.requestedHealthDataTypeNotAvailable)
return
}
let typeSet: Set = [quantitiyType]
// Request user access to HK and especially this type
healthStore.requestAuthorization(toShare: nil, read: typeSet)
{ success, error in
// Ensure that the app has the required access
if success == false
{
print(error?.localizedDescription ?? "<no error description>")
completion(nil, AppError.noAccessToRequestedInformation)
return
}
// Build query
let now = Date()
let lastSync = Calendar.current.startOfDay(for: now)
let prediction = HKQuery.predicateForSamples(withStart: lastSync, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: quantitiyType, quantitySamplePredicate: prediction, options: HKStatisticsOptions.discreteMostRecent)
{ _, statistics, error in
// Check for error.
if let _error = error
{
print("An error occured: \(_error.localizedDescription)")
completion(nil, AppError.requestingFailed)
return
}
// Check if statistics are available.
guard let _statistics = statistics else
{
completion(nil, AppError.requestingFailed)
return
}
completion(_statistics, nil)
}
// Execure query
healthStore.execute(query)
}
If you want to use just quantities, etc. Just use the HKSampleQuery instead of the HKStatisticsQuery.
After that, you just have to cast the result list of HKSample to a [HKQuantitySample].
Now you have the start and end date but also the quantity.

Delete health data that was previous stored from the same app in Swift?

Update Oct 7th
So after I read the answer, I now understand that I need to using query to retrive the data in Health and I try to using with codes:
let deletedType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCaffeine)
let predicate = HKQuery.predicateForSamples(withStart: dataDate as Date, end: dataDate as Date, options: .strictStartDate)
let findQuery = HKSampleQuery(sampleType: deletedType!, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) {
query, results, error in
if results != nil {
print("\nHere we got not nil on results!\n")
for result in (results as? [HKQuantitySample])! {
let quantity = result.quantity.doubleValue(for: HKUnit.gramUnit(with: .milli))
print(quantity)
}
} else {
print("results are nil")
return
}
}
healthKitStore.execute(findQuery)
I didn't do lot in the resultHander block, I firstly want to check what Data I found, and when I pring the quantity, I got noting, but I did get the "Here we got not nil on resluts" which means the results is not nil. I'm fresh to iOS developing and I check the document of HKHealthSample and cannot find which part of my HKSampleQuery wrong!
Original One:
I have an app that writes caffeine data into Health via HealthKit
Here is the save function
func saveCaffeine(_ caffeineRecorded: Double, dataDate: Date) {
// Set the quantity type to the running/walking distance.
let caffeineType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCaffeine)
// Set the unit of measurement to miles.
let caffeineQuantity = HKQuantity(unit: HKUnit.gramUnit(with: .milli), doubleValue: caffeineRecorded)
// Set the official Quantity Sample.
let caffeine = HKQuantitySample(type: caffeineType!, quantity: caffeineQuantity, start: dataDate, end: dataDate)
print("\n to be added: \(caffeine) \n")
// Save the distance quantity sample to the HealthKit Store.
healthKitStore.save(caffeine, withCompletion: { (success, error) -> Void in
if( error != nil ) {
print(error!)
} else {
print("The Caffeine has been recorded! Better go check!")
}
})
}
Then It saved succeddfully, after that I retrive the data when I delete from the table view and pass to another delete function :
func deleteCaffeine(_ caffeineRecorded: Double, dataDate: Date){
let caffeineType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.dietaryCaffeine)
let caffeineQuantity = HKQuantity(unit: HKUnit.gramUnit(with: .milli), doubleValue: caffeineRecorded)
let coffeeBeenDeleted = HKQuantitySample(type: caffeineType!, quantity: caffeineQuantity, start: dataDate, end: dataDate)
print("\n to be deleted: \(coffeeBeenDeleted) \n")
healthKitStore.delete(coffeeBeenDeleted, withCompletion: {(success, error) -> Void in
if (error != nil) {
print(error!)
} else {
print("This caffeine data just been deleted!")
}
})
}
Then I got the error: Error Domain=com.apple.healthkit Code=3 "Failed to find some objects for deletion."
I using Realm to manage the database, I write the data into it that I can then retrieve it.
When I add it the HQuantitySample been printed is:
to be added: 30 mg (2017-10-06 18:36:25 -0400 - 2017-10-06 18:36:25 -0400)
When I delete the same one, the HQuantitySample been printed is: to be deleted: 30 mg (2017-10-06 18:36:25 -0400 - 2017-10-06 18:36:25 -0400)
As I understand, it should retrieve the same data since the amount and date is all right. Am I misunderstand anything about delete in HealthKit
You can't delete an HKQuantitySample that was previously saved by constructing a new HKQuantitySample that has similar properties. If there were two caffeine samples in HealthKit with the same start date, end date, and quantity which one would you expect HealthKit to delete? Each HKObject is unique and to delete an object you must first find it using a query and then pass the object you got from the query to delete().
You need to define your specific key in HKMetadataKeySyncIdentifier before you save your data to apple health. And then, you can use HKMetadataKeySyncIdentifier to delete specific health data.
You can try my answer:
https://stackoverflow.com/a/69624769/8094919

iOS Apple HealthKit Apple Watch step data only

Is there a way to get only Apple Watch step data from the HealthKit database. I have worked through examples in Swift 3 and have working code to obtain the merged data which Apple supplies but that contains step data from both the iPhone and Apple Watch.
I have not seen any examples where anyone was able to separate the two pieces of data. Every example I have seen only gives the merged step data. I can iterate over the step data sources but cannot use the source identifier to obtain the step data for a specific calendar time period.
All the Swift 3 code I have seen is along the following lines (Objective-C code follows similar functionality):
// Request the step count.
let stepType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
//let predicate = HKQuery.predicateForObjects(from: watchsource)
let predicate = HKQuery.predicateForSamples(withStart: startdate, end: enddate, options: [])
// Order doesn't matter, as we are receiving a quantity.
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierEndDate, ascending:false)
// Fetch the steps.
let query = HKSampleQuery(sampleType:stepType!, predicate: predicate, limit: 0, sortDescriptors:[sortDescriptor]) { query, results, error in
var steps: Double = 0
if results != nil {
if results!.count > 0
{
for result in results as! [HKQuantitySample]
{
steps += result.quantity.doubleValue(for: HKUnit.count())
}
} // if results
} // if results != nil
completion(steps, error as NSError?)
} // HKSampleQuery()
healthKitStore.execute(query)
The above code as part of a proper HealthKit authentication works fine and I am able to obtain the step data for any time period.
However, there seems to be no way that HKSampleQuery() or another derivative HealthKit library call can be used for a particular source, ie, Apple Watch data only without the need for having an App on the Apple Watch itself to read the step data.
Yes, I know that one could read the iPhone pedometer data and then subtract that from the total number of steps but pedometer data is kept for only seven days, which is not much use if the time period is say, a month.
Has anyone solved this?
I was having the same issue. I wanted to get a separate step count for iPhone and Apple watch.
here is how I achieved this.
first, we need a predicate for devices to get only the Apple watch results.
let watchPredicate = HKQuery.predicateForObjects(withDeviceProperty: HKDevicePropertyKeyModel, allowedValues: ["Watch"])
now we can query for samples with this predicate and we'll just get the Apple watch entries.
we can also include more predicates to our query, using NSCompoundPredicate
Another approach is to fetch all the samples and then group step count values by the data source i.e. iPhone, Apple Watch, and third-party apps.
private lazy var store = HKHealthStore()
private func queryStepsCont(startDate: Date,
endDate: Date,
result: #escaping (Result<[(source: String, count: Int)], Error>) -> Void) {
guard let type = HKObjectType.quantityType(forIdentifier: .stepCount) else {
result(.failure(IntegrationsServiceError.preconditionFail))
return
}
let datePredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate)
let filteredByDateStepsCountQuery = HKSampleQuery(sampleType: type,
predicate: datePredicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: []) { _, samples, error in
if let error = error {
result(.failure(error))
return
}
let sourceNameStepsCount = Dictionary(grouping: samples ?? []) { sample in
sample.sourceRevision.source.name
}.map { sourceNameSamples -> (String, Int) in
let (sourceName, sourceSamples) = sourceNameSamples
let stepsCount = sourceSamples
.compactMap { ($0 as? HKQuantitySample)?.quantity.doubleValue(for: .count()) }
.reduce(0, +)
return (sourceName, Int(stepsCount))
}
result(.success(sourceNameStepsCount))
}
store.execute(filteredByDateStepsCountQuery)
}

Create new HKQuantityType

I've read in some pages that you can add custom samples to HealthKit in order to have another measurements saved.
In my case, I want to add accelerometer data from the apple watch to HealthKit.
This is my code
func saveSample(data:Double, date:NSDate ) {
let dataType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.init(rawValue: "acc"))
let dataQuantity = HKQuantity(unit: HKUnit.init(from: "m/s^2"), doubleValue: data)
let dataSample = HKQuantitySample(type: dataType!, quantity: dataQuantity, start: date as Date, end: date as Date)
healthKitStore.save(dataSample, withCompletion: { (success, error) -> Void in
if( error != nil ) {
print("Error saving sample:")
} else {
print("Sample saved successfully!")
}
})
}
I want to add a sample called "acc" (in a normal case one example of this could be "bloodPreasure") with unit "m/s^2".
I get nil on dataType, so then I get this Error on let dataSample = HKQuantitySample(type: dataType!, quantity: dataQuantity, start: date as Date, end: date as Date) line, because dataType is nil.
fatal error: unexpectedly found nil while unwrapping an Optional value
Any ideas,How to implement this? Thank u all!
I believe for HKQuantityType.quantityType(forIdentifier: we need to provide identifier provided by apple like HKQuantityTypeIdentifier.bodyTemperature. And then only it will return an object of quantityType.
So you are getting nil in dataType.
And I believe we can't create new HKQuantityType because health store will have to save it too, and that part is not in our control.

Resources