Welkin's Secret Garden

Access Steps Data in Two Cases

It is not hard to access users’ steps data once you know what APIs methods should be invoked. Nevertheless, here are the minimum requirements of the devices:

  1. Devices with motion coprocessor.
  2. iOS 7 or above.

And this is devided into two kinds:

  1. HealthKit supported. (iPhone 5s+ with iOS 8+, iPod Touch 6+)
  2. HealthKit unsupported. (iOS 7, iPad)

Aimed at these two kind, we need two different APIs to fetch users’ step counts.

HealthKit Supported

Apple first pushed HealthKit to its devices on iOS 8, for the purpose of collecting and gathering users’ state of health. Thus we can get what we want with HealthKit on iOS 8+.

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// For the reason that iPad and Apple Watch do not support HealthKit.
if ([HKHealthStore isHealthDataAvailable]) {
// Set what we want to get.
HKObjectType *stepCount = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSSet *readObjectTypes = [NSSet setWithObjects:stepCount, nil];
[self.healthStore requestAuthorizationToShareTypes:nil readTypes:readObjectTypes completion:^(BOOL success, NSError *error) {
// Attention, it returns "success" even if user deny the request.
if (success) {
NSDate *nowDate = [NSDate date];
NSDateComponents *rangeComps;
NSCalendar *rangeCalendar = [NSCalendar currentCalendar];
rangeComps = [rangeCalendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:nowDate];
rangeComps.year = 2015;
rangeComps.month = 11;
rangeComps.day = 22;
rangeComps.hour = 0;
rangeComps.minute = 0;
rangeComps.second = 0;
NSDate *beginDate = [rangeCalendar dateFromComponents:rangeComps];

rangeComps.year = 2016;
rangeComps.month = 8;
rangeComps.day = 31;
rangeComps.hour = 23;
rangeComps.minute = 59;
rangeComps.second = 59;
NSDate *endDate = [rangeCalendar dateFromComponents:rangeComps];

HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:beginDate endDate:endDate options:HKQueryOptionStrictStartDate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:YES];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) {
if(!error && results){
NSMutableDictionary *countsDict = [@{} mutableCopy];
// Merge steps by day.
for(HKQuantitySample *sample in results){
NSString *dateString = [UtilityFunction fetchDateString:sample.endDate];
if (!countsDict[dateString]) {
countsDict[dateString] = @0;
}
countsDict[dateString] = @([countsDict[dateString] integerValue] + [sample.quantity doubleValueForUnit:[HKUnit countUnit]]);
}
[countsDict enumerateKeysAndObjectsUsingBlock:^(NSString *dateString, NSNumber *dateCount, BOOL * _Nonnull stop) {
NSDictionary *dict = @{@"date" : dateString, @"count" : dateCount};
[counts addObject:dict];
}];
}
}];
[self.healthStore executeQuery:sampleQuery];
} else {
NSLog(@"failed");
}
}];
}

Through the practice, the steps data got will not be gathered by date automatically. They are simply shown item by item thus we need merge them manually.

HealthKit Unsupported

Because of the absence of HealthKit on iOS 7, we can merely fetch steps by CMStepCounter in the CoreMotion framework which is also owed to motion coprocessor which first appears in iPhone 5s. Well, we now know that we should confirm whether CMStepCounter is available.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if ([CMStepCounter isStepCountingAvailable]) {
NSDate *nowDate = [NSDate date];
NSDateComponents *comps;
NSCalendar *calendar = [NSCalendar currentCalendar];
comps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:nowDate];

for (int i = 0; i < 7; i += 1) {
comps.hour = 0;
comps.minute = 0;
comps.second = 0;
NSDate *startDate = [calendar dateFromComponents:comps];

comps.hour = 23;
comps.minute = 59;
comps.second = 59;
NSDate *endDate = [calendar dateFromComponents:comps];

[_stepCounter queryStepCountStartingFrom:startDate to:endDate toQueue:_queryQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (!error) {
NSString *monthStr = [NSString stringWithFormat:@"%02ld", (long)comps.month];
NSString *dayStr = [NSString stringWithFormat:@"%02ld", (long)comps.day];

NSDictionary *count = [@{} mutableCopy];
count[@"date"] = [NSString stringWithFormat:@"%li-%@-%@", (long)comps.year, monthStr, dayStr];
count[@"count"] = @(numberOfSteps);
[counts addObject:count];
}
}];
nowDate = [NSDate dateWithTimeInterval:-24 * 60 * 60 sinceDate:nowDate];
comps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:nowDate];
}
}

Unlike HealthKit, CMStepCounter simply allows you to tell him the start time and the end time and then returns you the result. For that, we can only perform the method in a for or while loops to gain several days data. Meanwhile, it is dispirited to tell you that we can merely get the data within 7 days with CMStepCounter. So, use HealthKit if possible!