Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get total step count for every date in HealthKit

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?

like image 922
MathiasMo Avatar asked Apr 11 '15 20:04

MathiasMo


5 Answers

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];
}
like image 116
James McNellis Avatar answered Nov 07 '22 02:11

James McNellis


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)
like image 38
sebastianr Avatar answered Nov 13 '22 13:11

sebastianr


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];
like image 25
ninjaproger Avatar answered Nov 13 '22 13:11

ninjaproger


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)
}
like image 27
anoop4real Avatar answered Nov 13 '22 13:11

anoop4real


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)
like image 1
horsejockey Avatar answered Nov 13 '22 15:11

horsejockey