I'm trying to fetch some data from a private ClouKit database. The query and predicate is working fine because I can see at print(data) // 1 that the array is filled with all the data. But as soon as it leaves perform(){} the array is resetted to it's at the top defined values and it just returns them.
func fetchUserRecord() -> [Double] {
var data: [Double] = [0, 1, 2] //demo data
let aWeekAgo = Date().addingTimeInterval(-604800)
let privateDB = CKContainer.default().privateCloudDatabase
let predicate = NSPredicate(format: "Date > %#", aWeekAgo as NSDate)
let query = CKQuery(recordType: "ProgressionValue", predicate: predicate)
privateDB.perform(query, inZoneWith: nil) { records, error in
guard let records = records else { return }
for record in records {
data.append(record["Value"] as! Double)
}
data.append(0.0)
print(data) // 1
}
print(data) // 2
return data
}
Here are the outputs:
print(data) //1:
[0.0, 1.0, 2.0, {tons of doubles}, 0.0]
print(data) //2:
[0.0, 1.0, 2.0]
What am I missing?
Try the new synch tools;
func fetchUserRecord() async throws -> [Double]
{
var data: [Double] = [0, 1, 2] // you don't really want the
let aWeekAgo = Date().addingTimeInterval(-604800)
let privateDB = CKContainer.default().privateCloudDatabase
let predicate = NSPredicate(format: "Date > %#", aWeekAgo as NSDate)
let query = CKQuery(recordType: "ProgressionValue", predicate: predicate)
let (values, cursor) = try await privateDB.records(matching: query, resultsLimit: 100)
for r in values
{
if let rec = try? r.1.get()
{
data.append(rec["value"] as Double)
}
}
return x
}
Call it like this;
Task
{
do {
data = try await fetchUserRecord()
}
catch
{
print(error)
}
}
I am now analyzing swift source code.
In this source code, performQuery is defined as function, but in line 2, performQuery is used without bracket"()". What does it mean?
Can we use a function without bracket?
I have read all the swift grammar but I cannot find similar code.
Is it related to "Unstructured Concurrency"?
==================================================
func calculateDailyQuantitySamplesForPastWeek() {
performQuery {
DispatchQueue.main.async { [weak self] in
self?.reloadData()
}
}
}
// MARK: - HealthQueryDataSource
func performQuery(completion: #escaping () -> Void) {
let predicate = createLastWeekPredicate()
let anchorDate = createAnchorDate()
let dailyInterval = DateComponents(day: 1)
let statisticsOptions = getStatisticsOptions(for: dataTypeIdentifier)
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: predicate,
options: statisticsOptions,
anchorDate: anchorDate,
intervalComponents: dailyInterval)
// The handler block for the HKStatisticsCollection object.
let updateInterfaceWithStatistics: (HKStatisticsCollection) -> Void = { statisticsCollection in
self.dataValues = []
let now = Date()
let startDate = getLastWeekStartDate()
let endDate = now
statisticsCollection.enumerateStatistics(from: startDate, to: endDate) { [weak self] (statistics, stop) in
var dataValue = HealthDataTypeValue(startDate: statistics.startDate,
endDate: statistics.endDate,
value: 0)
if let quantity = getStatisticsQuantity(for: statistics, with: statisticsOptions),
let identifier = self?.dataTypeIdentifier,
let unit = preferredUnit(for: identifier) {
dataValue.value = quantity.doubleValue(for: unit)
}
self?.dataValues.append(dataValue)
}
completion()
}
query.initialResultsHandler = { query, statisticsCollection, error in
if let statisticsCollection = statisticsCollection {
updateInterfaceWithStatistics(statisticsCollection)
}
}
query.statisticsUpdateHandler = { [weak self] query, statistics, statisticsCollection, error in
// Ensure we only update the interface if the visible data type is updated
if let statisticsCollection = statisticsCollection, query.objectType?.identifier == self?.dataTypeIdentifier {
updateInterfaceWithStatistics(statisticsCollection)
}
}
self.healthStore.execute(query)
self.query = query
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let query = query {
self.healthStore.stop(query)
}
}
}
you need to learn closure. please check the link
I am trying to get last 7 days step count but it is always coming zero. But when I open the health app in iPhone then it is more than 3000 steps. Even I also added property Privacy - Health Share Usage Description and Privacy - Health Update Usage Description in .plist file.
Here is my code
var healthScore = HKHealthStore()
override func viewDidLoad() {
super.viewDidLoad()
// Access Step Count
let healthKitTypes: Set = [ HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)! ]
// Check for Authorization
healthScore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (bool, error) in
if (bool) {
// Authorization Successful
self.getSteps { (result) in
DispatchQueue.main.async {
let stepCount = String(Int(result))
self.stepLbl.text = String(stepCount)
}
}
}
}
}
func getSteps(completion: #escaping (Double) -> Void){
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let exactlySevenDaysAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: now)!
let predicate = HKQuery.predicateForSamples(withStart: exactlySevenDaysAgo, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepsQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
print("\(String(describing: error?.localizedDescription)) ")
completion(resultCount)
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
}
DispatchQueue.main.async {
completion(resultCount)
}
}
healthScore.execute(query)
}
In console I checked the dates are also correct now here's the screenshot
This question already has answers here:
How to notify a queue in Swift (GCD)
(2 answers)
Closed 4 years ago.
I have this identical code in another app and it works without fail, now in this app the dispatch group is not notifying and my handler isn't getting called? I can't figure out the difference between the 2 apps since the code is identical?
func getHeartRateMaxFromWorkouts(workouts: [HKWorkout], handler: #escaping (careerMaxHeartRatePerWorkoutAsCustomHistoricalSample, careerAverageHeartRatePerWorkoutAsCustomHistoricalSample) -> Void) {
let workoutsReversed = workouts.reversed()
guard let heartRateType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return }
let heartRateUnit:HKUnit = HKUnit(from: "count/min")
var heartRateMaxArrayAsCustomHistoricalSample = [CustomHistoricalSample]()
var heartRateAvgArrayAsCustomHistoricalSample = [CustomHistoricalSample]()
//DispatchGroup needed since making async call per workout and need notified when all calls are done
let dispatchGroup = DispatchGroup()
for workout in workoutsReversed {
//predicate
let startDate = workout.startDate
let endDate = workout.endDate
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
//descriptor
let sortDescriptors = [
NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true) //Changed this to false so that HRR and MCS would calculate, always check this if not getting these values
]
dispatchGroup.enter()
let heartRateQuery = HKSampleQuery(sampleType: heartRateType,
predicate: predicate,
limit: (HKObjectQueryNoLimit),
sortDescriptors: sortDescriptors)
{ (query:HKSampleQuery, results:[HKSample]?, error:Error?) -> Void in
guard error == nil else { print("get heart rate error"); return }
guard let unwrappedResults = results as? [HKQuantitySample] else { print("get heart rate error"); return}
let heartRatesAsDouble = unwrappedResults.map {$0.quantity.doubleValue(for: heartRateUnit)}
guard let max = heartRatesAsDouble.max() else { return }
let maxAsCustomHistoricalSample = CustomHistoricalSample(value: max, date: workout.startDate)
heartRateMaxArrayAsCustomHistoricalSample.append(maxAsCustomHistoricalSample)
let average = heartRatesAsDouble.average
let averageAsCustomHistoricalSample = CustomHistoricalSample(value: average, date: workout.startDate)
heartRateAvgArrayAsCustomHistoricalSample.append(averageAsCustomHistoricalSample)
dispatchGroup.leave()
}
healthStore.execute(heartRateQuery)
} //End of for workout loop
dispatchGroup.notify(queue: .main) {
//Need to sort by date since the dates come back jumbled
let sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple = heartRateMaxArrayAsCustomHistoricalSample.sorted { $0.date > $1.date }.reversed() as [CustomHistoricalSample]
let sortedReversedHeartRateAverageArrayAsCustomHistoricalSampple = heartRateAvgArrayAsCustomHistoricalSample.sorted { $0.date > $1.date }.reversed() as [CustomHistoricalSample]
print("handler called = \(sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple.count)")
handler(sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple, sortedReversedHeartRateAverageArrayAsCustomHistoricalSampple)
}
} //End getHeartRateMaxFromWorkouts
It's a good practice to make leave top of callback
guard error == nil else { print("get heart rate error"); dispatchGroup.leave() return ; }
guard let unwrappedResults = results as? [HKQuantitySample] else { print("get heart rate error"); dispatchGroup.leave(); return}
let heartRatesAsDouble = unwrappedResults.map {$0.quantity.doubleValue(for: heartRateUnit)}
guard let max = heartRatesAsDouble.max() else { dispatchGroup.leave(); return }
let maxAsCustomHistoricalSample = CustomHistoricalSample(value: max, date: workout.startDate)
heartRateMaxArrayAsCustomHistoricalSample.append(maxAsCustomHistoricalSample)
let average = heartRatesAsDouble.average
let averageAsCustomHistoricalSample = CustomHistoricalSample(value: average, date: workout.startDate)
dispatchGroup.leave()
heartRateAvgArrayAsCustomHistoricalSample.append(averageAsCustomHistoricalSample)
Here is code for saving blood pressure data in health kit
HKUnit *BPunit = [HKUnit millimeterOfMercuryUnit];
HKQuantity *BPSysQuantity = [HKQuantity quantityWithUnit:BPunit doubleValue:150.0];
HKQuantityType *BPSysType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic];
HKQuantitySample *BPSysSample = [HKQuantitySample quantitySampleWithType:BPSysType quantity:BpsysQuantity startDate:now endDate:now];
[self.healthStore saveObject:BPSysSample withCompletion:^(BOOL success, NSError *error)
same way for diastolic also,
But how to save both combine as single entry in health app? Currently two diffrent entries are saved for systolic and diastolic blood pressure in health app.
- (void)saveBloodPressureIntoHealthStore:(double)Systolic Dysbp:(double)Diastolic {
HKUnit *BloodPressureUnit = [HKUnit millimeterOfMercuryUnit];
HKQuantity *SystolicQuantity = [HKQuantity quantityWithUnit:BloodPressureUnit doubleValue:Systolic];
HKQuantity *DiastolicQuantity = [HKQuantity quantityWithUnit:BloodPressureUnit doubleValue:Diastolic];
HKQuantityType *SystolicType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureSystolic];
HKQuantityType *DiastolicType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodPressureDiastolic];
NSDate *now = [NSDate date];
HKQuantitySample *SystolicSample = [HKQuantitySample quantitySampleWithType:SystolicType quantity:SystolicQuantity startDate:now endDate:now];
HKQuantitySample *DiastolicSample = [HKQuantitySample quantitySampleWithType:DiastolicType quantity:DiastolicQuantity startDate:now endDate:now];
NSSet *objects=[NSSet setWithObjects:SystolicSample,DiastolicSample, nil];
HKCorrelationType *bloodPressureType = [HKObjectType correlationTypeForIdentifier:
HKCorrelationTypeIdentifierBloodPressure];
HKCorrelation *BloodPressure = [HKCorrelation correlationWithType:bloodPressureType startDate:now endDate:now objects:objects];
[self.healthStore saveObject:BloodPressure withCompletion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"An error occured saving the height sample %#. In your app, try to handle this gracefully. The error was: %#.", BloodPressure, error);
abort();
}
[_activity stopAnimating];
UIAlertView *savealert=[[UIAlertView alloc]initWithTitle:#"HealthDemo" message:#"Blood Pressure values has been saved to Health App" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[savealert show];
}];
}
In Swift 3:
func saveBloodPressure(systolic systolicValue: Double, diastolic diastolicValue: Double, completion completionBlock: #escaping (Bool, Error?) -> Void) {
let unit = HKUnit.millimeterOfMercury()
let systolicQuantity = HKQuantity(unit: unit, doubleValue: systolicValue)
let diastolicQuantity = HKQuantity(unit: unit, doubleValue: diastolicValue)
let systolicType = HKQuantityType.quantityType(forIdentifier: .bloodPressureSystolic)!
let diastolicType = HKQuantityType.quantityType(forIdentifier: .bloodPressureDiastolic)!
let nowDate = Date()
let systolicSample = HKQuantitySample(type: systolicType, quantity: systolicQuantity, start: nowDate, end: nowDate)
let diastolicSample = HKQuantitySample(type: diastolicType, quantity: diastolicQuantity, start: nowDate, end: nowDate)
let objects: Set<HKSample> = [systolicSample, diastolicSample]
let type = HKObjectType.correlationType(forIdentifier: .bloodPressure)!
let correlation = HKCorrelation(type: type, start: nowDate, end: nowDate, objects: objects)
self.healthKitStore.save(correlation) { (success, error) -> Void in
if !success {
print("An error occured saving the Blood pressure sample \(systolicSample). In your app, try to handle this gracefully. The error was: \(error).")
}
completionBlock(success, error)
}
}
Check out HKCorrelation. A correlation is a set of related objects and is designed to represent things like blood pressure readings and food. You can save create and save correlations just like samples and you can query for correlations using HKCorrelationQuery.
Swift : iOS : Save Blood Pressure:
private func saveBloodPressureIntoHealthStore(bloodPressureValueSystolic:Double
,bloodPressureValueDiastolic:Double) -> Void {
// Save the user's blood pressure into HealthKit.
let bloodPressureUnit: HKUnit = HKUnit.millimeterOfMercuryUnit()
let bloodPressureSystolicQuantity: HKQuantity = HKQuantity(unit: bloodPressureUnit, doubleValue: bloodPressureValueSystolic)
let bloodPressureDiastolicQuantity: HKQuantity = HKQuantity(unit: bloodPressureUnit, doubleValue: bloodPressureValueDiastolic)
let bloodPressureSystolicType: HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodPressureSystolic)
let bloodPressureDiastolicType: HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodPressureDiastolic)
let nowDate: NSDate = NSDate()
let bloodPressureSystolicSample: HKQuantitySample = HKQuantitySample(type: bloodPressureSystolicType
, quantity: bloodPressureSystolicQuantity, startDate: nowDate, endDate: nowDate)
let bloodPressureDiastolicSample: HKQuantitySample = HKQuantitySample(type: bloodPressureDiastolicType
, quantity: bloodPressureDiastolicQuantity, startDate: nowDate, endDate: nowDate)
let completion: ((Bool, NSError!) -> Void) = {
(success, error) -> Void in
if !success {
println("An error occured saving the Blood pressure sample \(bloodPressureSystolicSample). In your app, try to handle this gracefully. The error was: \(error).")
abort()
}
}// end completion
var objects : NSSet = NSSet(objects: bloodPressureSystolicSample,bloodPressureDiastolicSample)
var bloodPressureType: HKCorrelationType = HKObjectType.correlationTypeForIdentifier(HKCorrelationTypeIdentifierBloodPressure)
var bloodPressureCorrelation : HKCorrelation = HKCorrelation(type: bloodPressureType, startDate: nowDate
, endDate: nowDate, objects: objects)
self.healthStore!.saveObject(bloodPressureCorrelation, withCompletion: completion)
}// end saveBloodPressureIntoHealthStore
Xamarin.iOS solution
public void SaveBloodPressure(DateTime date, double systolic, double diastolic, double beatsPerMinute)
{
using (var healthKitStore = new HKHealthStore())
{
var heartRateUnitType = HKUnit.MillimeterOfMercury;
var diastolicQuantity = HKQuantity.FromQuantity(heartRateUnitType, diastolic);
var diastolicQuantityType = HKQuantityType.GetQuantityType(HKQuantityTypeIdentifierKey.BloodPressureDiastolic);
var diastolicSample = HKQuantitySample.FromType(diastolicQuantityType, diastolicQuantity, date.ToUniversalTime().ToNSDate(), date.ToUniversalTime().ToNSDate(), new HKMetadata());
var systolicQuantity = HKQuantity.FromQuantity(heartRateUnitType, systolic);
var systolicQuantityType = HKQuantityType.GetQuantityType(HKQuantityTypeIdentifierKey.BloodPressureSystolic);
var systolicSample = HKQuantitySample.FromType(systolicQuantityType, systolicQuantity, date.ToUniversalTime().ToNSDate(), date.ToUniversalTime().ToNSDate(), new HKMetadata());
var objects = new NSSet(systolicSample, diastolicSample);
var bloodPressureType = HKCorrelationType.GetCorrelationType(HKCorrelationTypeKey.IdentifierBloodPressure);
var bloodPressure = HKCorrelation.Create(bloodPressureType, date.ToUniversalTime().ToNSDate(), date.ToUniversalTime().ToNSDate(), objects);
try
{
healthKitStore.SaveObject(bloodPressure, (success, error) =>
{
//action to take on success/failure
});
}
catch (Exception)
{
//handle exception
}
try
{
var beatsPerMinuteUnits = HKUnit.Count.UnitDividedBy(HKUnit.Minute);
var beatsPerMinuteQuantity = HKQuantity.FromQuantity(beatsPerMinuteUnits, beatsPerMinute);
var heartRateQuantityType = HKQuantityType.GetQuantityType(HKQuantityTypeIdentifierKey.HeartRate);
var heartRateSample = HKQuantitySample.FromType(heartRateQuantityType, beatsPerMinuteQuantity, date.ToUniversalTime().ToNSDate(), date.ToUniversalTime().ToNSDate(), new HKMetadata());
healthKitStore.SaveObject(heartRateSample, (success, error) =>
{
//handle success / failure
});
}
catch (Exception)
{
//handle exception
}
}
}