Get total step count for every date in HealthKit
Asked Answered
C

6

10

What's the best way to get a total step count for every day recorded in HealthKit. With HKSampleQuery's method initWithSampleType (see below) I can set a start and end date for the query using NSPredicate, but the method returns an array with many HKQuantitySamples per day.

- (instancetype)initWithSampleType:(HKSampleType *)sampleType
                     predicate:(NSPredicate *)predicate
                         limit:(NSUInteger)limit
               sortDescriptors:(NSArray *)sortDescriptors
                resultsHandler:(void (^)(HKSampleQuery *query,
                                         NSArray *results,
                                         NSError *error))resultsHandler

I guess I can query all recorded step counts and go through the array and calculate the total step count for each day, but I'm hoping for an easier solution as there will be thousands of HKSampleQuery objects. Is there a way to have initWithSampleType return a total step count per day?

Coacervate answered 11/4, 2015 at 20:4 Comment(0)
G
17

You should use HKStatisticsCollectionQuery:

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 1;

NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
                                                 fromDate:[NSDate date]];
anchorComponents.hour = 0;
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
HKQuantityType *quantityType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

// Create the query
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType
                                                                       quantitySamplePredicate:nil
                                                                                       options:HKStatisticsOptionCumulativeSum
                                                                                    anchorDate:anchorDate
                                                                            intervalComponents:interval];

// Set the results handler
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
    if (error) {
        // Perform proper error handling here
        NSLog(@"*** An error occurred while calculating the statistics: %@ ***",error.localizedDescription);
    }

    NSDate *endDate = [NSDate date];
    NSDate *startDate = [calendar dateByAddingUnit:NSCalendarUnitDay
                                             value:-7
                                            toDate:endDate
                                           options:0];

    // Plot the daily step counts over the past 7 days
    [results enumerateStatisticsFromDate:startDate
                                  toDate:endDate
                               withBlock:^(HKStatistics *result, BOOL *stop) {

                                   HKQuantity *quantity = result.sumQuantity;
                                   if (quantity) {
                                       NSDate *date = result.startDate;
                                       double value = [quantity doubleValueForUnit:[HKUnit countUnit]];
                                       NSLog(@"%@: %f", date, value);
                                   }

                               }];
};

[self.healthStore executeQuery:query];
Goldiegoldilocks answered 11/4, 2015 at 21:11 Comment(5)
This looks great. Tried to translate to swift without success. Anyone have a translation?Kunkel
@Kunkel i translated it to swift. not sure if it is too late. cheers.Holter
I don't know if it's a bug in HealthKit or what, but this query never returns for me for steps and distance on my device.Surrebutter
The code of this page: developer.apple.com/reference/healthkit/…Rorry
Every thing is working fine I just add anchorComponents.minute = 330; // Adding 5:30 hours for getting correct day result including today.Piecrust
G
9

Port to Swift with no dependency to SwiftDate library

    let calendar = NSCalendar.current
    let interval = NSDateComponents()
    interval.day = 1

    var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
    anchorComponents.hour = 0
    let anchorDate = calendar.date(from: anchorComponents)

    // Define 1-day intervals starting from 0:00
    let stepsQuery = HKStatisticsCollectionQuery(quantityType: stepsCount!, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: interval as DateComponents)

    // Set the results handler
    stepsQuery.initialResultsHandler = {query, results, error in
        let endDate = NSDate()
        let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, wrappingComponents: false)
        if let myResults = results{
            myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
            if let quantity = statistics.sumQuantity(){
                let date = statistics.startDate
                let steps = quantity.doubleValue(for: HKUnit.count())
                print("\(date): steps = \(steps)")
                //NOTE: If you are going to update the UI do it in the main thread
                DispatchQueue.main.async {
                    //update UI components
                }

            }
            } //end block
        } //end if let
    }
    healthStore?.execute(stepsQuery)
Glacialist answered 10/8, 2016 at 14:14 Comment(0)
K
2

Modified @sebastianr's answer using core Swift classes, for just for testing I am returning only steps for just one day, once you have more days you can create a dictionary of Dates and step count and return it

func getStepCountPerDay(completion:@escaping (_ count: Double)-> Void){

    guard let sampleType = HKObjectType.quantityType(forIdentifier: .stepCount)
        else {
            return
    }
    let calendar = Calendar.current
    var dateComponents = DateComponents()
    dateComponents.day = 1

    var anchorComponents = calendar.dateComponents([.day, .month, .year], from: Date())
    anchorComponents.hour = 0
    let anchorDate = calendar.date(from: anchorComponents)

    let stepsCumulativeQuery = HKStatisticsCollectionQuery(quantityType: sampleType, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: dateComponents
    )

    // Set the results handler
    stepsCumulativeQuery.initialResultsHandler = {query, results, error in
        let endDate = Date()
        let startDate = calendar.date(byAdding: .day, value: 0, to: endDate, wrappingComponents: false)
        if let myResults = results{
            myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
                if let quantity = statistics.sumQuantity(){
                    let date = statistics.startDate
                    let steps = quantity.doubleValue(for: HKUnit.count())
                    print("\(date): steps = \(steps)")
                    completion(steps)
                    //NOTE: If you are going to update the UI do it in the main thread
                    DispatchQueue.main.async {
                        //update UI components
                    }
                }
            } //end block
        } //end if let
    }
    HKHealthStore().execute(stepsCumulativeQuery)
}
Klepac answered 6/11, 2017 at 19:54 Comment(0)
H
1

Here is a translation that currently works for Swift 2.0, using the SwiftDate library.

    let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
    let startDate = NSDate().beginningOfDay().oneWeekAgo()
    let interval = NSDateComponents()
    interval.day = 1

    let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
    let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().begginingOfDay(), intervalComponents:interval)

    query.initialResultsHandler = { query, results, error in


        let endDate = NSDate()
        let startDate = NSDate().beginningOfDay().oneWeekAgo()
        if let myResults = results{
            myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
                statistics, stop in

                if let quantity = statistics.sumQuantity() {

                    let date = statistics.startDate
                    let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
                    print("\(date): steps = \(steps)")
                }
            }
        }
    }

    healthKitStore.executeQuery(query)
Holter answered 2/7, 2015 at 18:7 Comment(1)
I don't think it's a good idea to give a Swift translation requiring an additional library - but no offenseAlwin
V
1

I wrapped mine in a completion block (objective -c). I found what was best was to set the startDate for the query to todays date at midnight. Hope this helps, feel free to copy/paste to get started

-(void)fetchHourlyStepsWithCompletionHandler:(void (^)(NSMutableArray *, NSError *))completionHandler {
     NSMutableArray *mutArray = [NSMutableArray new];
     NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];

     NSDate *startDate = [calendar dateBySettingHour:0 minute:0 second:0 ofDate:[NSDate date] options:0];

     NSDate *endDate = [NSDate date]; // Whatever you need in your case
     HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

    // Your interval: sum by hour
     NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
     intervalComponents.hour = 1;

   // Example predicate
     NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];

     HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:startDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
    [results enumerateStatisticsFromDate:startDate toDate:endDate
     withBlock:^(HKStatistics *result, BOOL *stop) {
         if (!result) {
             if (completionHandler) {
                 completionHandler(nil, error);
             }
             return;
         }
         
         HKQuantity *quantity = result.sumQuantity;
         
         NSDate *startDate = result.startDate;
       
         NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
         formatter.dateFormat = @"h a";
        
         NSString *dateString = [formatter stringFromDate:startDate]; 
         
         double steps = [quantity doubleValueForUnit:[HKUnit countUnit]];
         
         NSDictionary *dict = @{@"steps" : @(steps),
                                @"hour" : dateString
                                };
         
         [mutArray addObject:dict];
     }];
    
    if (completionHandler) {
        completionHandler(mutArray, error);
    }
};
    [self.healthStore executeQuery:query];
}
Viva answered 25/1, 2017 at 0:19 Comment(0)
A
-1

With Updated Swift 2.0 & SwiftDate library.

let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let startDate = NSDate().beginningOfDay
let interval = NSDateComponents()
interval.day = 1

let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().beginningOfDay, intervalComponents:interval)

query.initialResultsHandler = { query, results, error in


  let endDate = NSDate()
  let startDate = NSDate().beginningOfDay
  if let myResults = results{
    myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
      statistics, stop in

      if let quantity = statistics.sumQuantity() {

        let date = statistics.startDate
        let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
        print("\(date): steps = \(steps)")
      }
    }
  }
}
healthKitStore.executeQuery(query)
Atory answered 8/10, 2015 at 9:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.