7. Implementing HealthKit

People all over the world are becoming increasingly interested and concerned about their personal health, and they are leveraging technology to gain valuable insight. The rise of smartphones enabled common consumers to carry a computer with them wherever they went, and it wasn’t long before a rush of third-party fitness and health apps followed.

Health-focused mobile apps were a $2.4 billion industry in 2013, and this is projected to grow to more than $20 billion by 2017. Each quarter more than 100,000 new health apps are published to the iTunes App Store, and until HealthKit it was complete chaos. Due to the sandboxing nature of the iOS platform, these apps haven’t been able to share data with other third-party apps. This does not allow a user’s sleep-tracking app to monitor his weight loss or exercise routines, or even allow his running app to track his nutritional intake.

HealthKit fixes this problem by providing a secure centralized location for the user’s health information, from body measurements to workout data, and even food intake. This not only allows third-party apps to share data with each other, but, more important, makes it easy for a user to switch apps. Up until now, users were easily locked into a particular app since their data was all stored there; now they are free to switch to new and better solutions without the risk of losing their historical records. This is fantastic news for developers, because it removes a giant barrier to entry that was preventing new fitness and health apps to flourish.

Introduction to HealthKit

HealthKit was introduced with the rest of iOS 8 at Apple’s WWDC 2014. At its core, HealthKit is a framework that allows apps to share and access health and fitness data. HealthKit also provides a system app that will be bundled with all new iOS 8 devices. This new Health app allows a user to see a consolidated dataset of all their health data taken from all compliant apps.

Specially designed HealthKit hardware devices also allow the direct storage of biometric data as well as directly interacting with newer iOS devices, which feature the M7 motion coprocessor chip. These hardware interactions are outside of the scope of this chapter; however, more information can be found in the HealthKit framework guide located at https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/index.html#//apple_ref/doc/uid/TP40014707.

It is important to remember that when working with HealthKit, the developer has access to personal and private information about the user. HealthKit is a permission-based service, and the user will need to grant the app permission to access each requested piece of information. After the app has access to the information, it is the developer’s responsibility to treat that information as highly confidential and sensitive. A good guideline is to treat any health data as you would someone’s credit card or Social Security information.

Introduction to Health.app

Before you can write a HealthKit-enabled app, you must first understand Health.app (see Figure 7.1), which comes bundled with iOS 8. Health.app provides the centralized access point to all the user’s health data. Users are able to configure their Dashboard to see the metrics that they are interested in; for example, in Figure 7.1, the user is viewing steps, walking and running distance, and sleep analysis. The Health Data section further allows the users to see the individual stored data for every point of entry as well as letting them delete entries they no longer want to retain. The Sources tab allows users to configure which apps have access to which data, which is covered in the section “Requesting Permission for Health Data.” Finally, the Medical ID tab allows the user to enter information that might be useful to first responders, such as allergies, emergency contact info, blood type, and medications. This Medical ID information is accessible from the lock screen without the user’s passcode.

Image

Figure 7.1 Dashboard section of the iOS Health.app.

The Sample App

The sample app that accompanies this chapter is called ICFFever. ICFFever is a simple HealthKit app that demonstrates the storage and retrieval of basic user info, such as age, height, and weight (see Figure 7.2). Additionally, the app showcases storing, retrieving, sorting, and working with body temperature data (see Figure 7.3). Although the functionality of the app has been kept simple to avoid overcomplication, it will showcase all the required key functionality of interacting with HealthKit data.

Image

Figure 7.2 ICFFever Profile information entry screen.

Image

Figure 7.3 ICFFever entering and viewing body temperature information.

Adding HealthKit to a Project

HealthKit is a standalone framework, and a new project must first be configured to work with HealthKit. This includes adding the proper entitlements, adding a key in your info plist to flag the app as HealthKit enabled, and linking to the HealthKit framework. Apple makes it easy to correctly enable all the proper settings in Xcode 6. While your project is open, select the project icon item from the project navigator and then navigate to the Capabilities section. Toggling the switch next to the HealthKit item will automatically configure your project.

In each class that will be working with HealthKit, the proper header will need to be first imported.

@import HealthKit;

HealthKit is a permission-based service; before the app can access or write any information, the user will need to grant his permission. To help facilitate this process, a single instance of an HKHealthStore is created in the sample app’s app delegate. Each view controller will reference back to this object.

@property (nonatomic) HKHealthStore *healthStore;

ICFFever is a tab bar-based app and each tab will need to have access to the HKHealthStore. Inside the app delegate a method is provided which will populate that shared object to each of the view controllers. Depending on the nature and setup of each unique app, approaches to this might vary. Each view controller will need to create its own property for healthStore, which is demonstrated in the sample app.

 (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
              self.healthStore = [[HKHealthStore alloc] init];

              UITabBarController *tabBarController = (UITabBarController *) [self.window rootViewController];

                 for (UINavigationController *navigationController in tabBarController.viewControllers)
                 {
                     id viewController = navigationController;

                    if ([viewController respondsToSelector:@ selector(setHealthStore:)])
                    {
                        [viewController setHealthStore:self.healthStore];
                    }
                 }

                 return YES;
}

Requesting Permission for Health Data

After the groundwork has been laid to begin working with HealthKit, the app can request permission to access specific health data. This is a multiple-stage process, beginning with a simple check to make sure that HealthKit is available on the current device.

if ([HKHealthStore isHealthDataAvailable])

The next step is to generate a list of all the datasets the app will need to read and write to. Each point of information must be specifically requested and the user can opt to approve some but not all the data points. The sample app breaks this list generation into two convenience methods that will each return an NSSet.

// Returns the types of data that the app wants to write to HealthKit.
- (NSSet *)dataTypesToWrite
{
        HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];

        HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];

        HKQuantityType *tempType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];

        return [NSSet setWithObjects:tempType, heightType, weightType, nil];
}

// Returns the types of data that the app wants wishes to read from HealthKit.
- (NSSet *)dataTypesToRead
{
    HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];

    HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];

    HKQuantityType *tempType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];

    HKCharacteristicType *birthdayType = [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth];

    return [NSSet setWithObjects:heightType, weightType, birthdayType, tempType, nil];
}

ICFFever will request permission to write height, weight, and body temperature data. It will also be asking for permission to read height, weight, body temperature, and birthdate information. Keep in mind that it is possible for the app to be allowed to write data it doesn’t have access to read, and to read data it doesn’t have access to write.


Note

HealthKit contains a type of data point called a characteristic (HKCharacteristicType); these characteristics include gender, blood type, and birthday. HealthKit does not allow a third-party app to enter changes to these data points; changes and initial entry to them must be made within the Health.app, although third-party apps may read the data with the user’s permission.


More than 70 types of data are currently available from HealthKit, ranging from items as common as weight to more detailed items such as inhaler usage and oxygen saturation. These items are readily available in the HealthKit documentation and in an effort to save trees (or bytes for e-books); they are not listed in this chapter.

After the app has built two NSSets, one for reading data and one for writing, the app is ready to prompt the user for permission. The user is presented with a HealthKit permission request screen (see Figure 7.4) and will then select the permission set the user wants to grant. A user can make further changes to these permissions from within the Health.app at any time.

if ([HKHealthStore isHealthDataAvailable])
{
        NSSet *writeDataTypes = [self dataTypesToWrite];
        NSSet *readDataTypes = [self dataTypesToRead];

       [self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
            if (!success)
            {
                NSLog(@"HealthKit was not properly authorized to be added, check entitlements and permissions. Error: %@", error);

                return;
            }
       }];
}

Image

Figure 7.4 A HealthKit permission request for writing body temperature, height, and weight, as well as reading body temperature, date of birth, height, and weight.

Reading Characteristic HealthKit Data

Assuming that the user has granted permission to read the data from the Health.app, the app can now begin to work with data that is already stored. The first tab of the sample app shows the user basic profile information. Because the date of birth cannot be modified from within a third-party app, that is a logical place to start with simple data reading. A new method called updateAge is created in the sample app. HealthKit provides a convenience method for quickly retrieving the user’s date of birth named dateOfBirthWithError. Likewise, methods for the other characteristics data gender and blood type exist. The method follows basic error checking flow and, if no entry is found, lets the user know to enter her birthdate into the Health.app. After the data is retrieved as an NSDate, the year is stripped out of it and displayed to the user.

- (void)updateAge
{
    NSError *error = nil;
    NSDate *dateOfBirth = [self.healthStore dateOfBirthWithError:&error];

    if (!dateOfBirth)
    {
        NSLog(@"No age was found");
        dispatch_async(dispatch_get_main_queue(), ^{
                  self.ageTextField.placeholder = @"Enter in HealthKit App";
        });
    }

    else
    {
        NSDate *now = [NSDate date];

        NSDateComponents *ageComponents = [[NSCalendar currentCalendar]
        components:NSCalendarUnitYear fromDate:dateOfBirth toDate:now
        options:NSCalendarWrapComponents];

        NSUInteger usersAge = [ageComponents year];

        self.ageTextField.text = [NSNumberFormatter localizedStringFromNumber: @(usersAge) numberStyle:NSNumberFormatterNoStyle];
    }
}

Reading and Writing Basic HealthKit Data

The sample app also enables the user to not only read in height and weight data but also provide updates to that data from within the app itself. ICFFever breaks this down into two separate steps; the first step is reading the data and the second is writing new data. To retrieve a new data point, first an HKQuanyityType object is created using the data point that will be retrieved, in the following example HKQuantityTypeIdentifierBodyMass.

Apple has graciously provided a convenience method for retrieving the most recent sample of an entry type; this method is included in the class HKHealthStore+AAPLExtensions.m. Assuming that there is weight data in the HealthKit data store, an HKQuantity object will be returned. The sample app specifies the units it would like to display. Here an HKUnit for a poundUnit is created, though the data could also be returned in grams, ounces, stones, or even molar mass. A double value is taken from the HKQuantity object after it has been specified what the data should be in pounds. That data is then displayed through a main thread dispatch, since HealthKit runs on a background thread.

- (void)updateWeight
{
         HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];

         [self.healthStore aapl_mostRecentQuantitySampleOfType:weightType predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSError *error)
        {
                if (!mostRecentQuantity)
                {
                        NSLog(@"No weight was found");

                        dispatch_async(dispatch_get_main_queue(), ^{
                               self.weightTextField.placeholder = @"Enter in lbs";
                        });
                }
        else
        {
                HKUnit *weightUnit = [HKUnit poundUnit];
                double usersWeight = [mostRecentQuantity doubleValueForUnit:weightUnit];

                dispatch_async(dispatch_get_main_queue(), ^{
                    self.weightTextField.text = [NSNumberFormatter localizedStringFromNumber:@(usersWeight) numberStyle:NSNumberFormatterNoStyle];
                });
        }
    }];
}

It is likely that the user does not have data saved for his weight yet; the ICFFever app also enables the user to save new weight data. Saving data is very similar to retrieving data; first an HKUnit needs to be defined to indicate what type of unit the data is being saved in. In the sample app the user will enter his weight in pounds.

A new HKQuantity is created with the type of unit and the value that is being saved. Next an HKQuanityType is created specifying which type of data point is being stored. Since the data is a type of weight, the HKQuantityTypeIdentifierBodyMass constant is used. Each data point is saved with a time stamp so that information can be charted and compared in the future; the assumption with the ICFFever app is that entered weight and height data is current. A new HKQuantitySample is created using the type, quantity, and date range. The health store object then calls saveObject and specifies a completion block. The method then can call the earlier method to display the newly entered weight.

        (void)saveWeightIntoHealthStore:(double)weight
{
                HKUnit *poundUnit = [HKUnit poundUnit];

                HKQuantity *weightQuantity = [HKQuantity quantityWithUnit:poundUnit doubleValue:weight];

                HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];

                NSDate *now = [NSDate date];

                  HKQuantitySample *weightSample = [HKQuantitySample quantitySampleWithType:weightType quantity:weightQuantity startDate:now endDate:now];

                 [self.healthStore saveObject:weightSample withCompletion: ^(BOOL success, NSError *error)
                 {
                        if (!success)
                        {
                             NSLog(@"An error occurred saving weight (%@): %@.", weightSample, error);
                        }

                        [self updateWeight];
                }];
}

The profile screen also enables the user to save and retrieve height information. The process is almost identical to the approach for weight, with a few differences. For height the app specifies an inch unit instead of a pound unit, and the HKQuantity type is HKQuantityTypeIdentifierHeight instead of HKQuantityTypeIdentifierBodyMass.

Reading and Writing Complex HealthKit Data

The preceding section discussed reading and writing very basic profile data such as height and weight information from or to HealthKit. This section dives deeper into data types and working with a more complex dataset, body temperature. The second tab of the ICFFever app enables the user to store current body temperature in either Celsius or Fahrenheit. The app will output not only the most recent body temperature but also the highest, lowest, and average over the past seven days.

HealthKit provides three unit types for working with body temperature, Kelvin, Fahrenheit, and Celsius. Since the app will be switching between Celsius and Fahrenheit, the data will be stored in Kelvin and then converted to the user’s selection to display. Specifying a new data type each time the information is stored or retrieved can also solve this problem. There are merits to either system but working in a single unit should be less confusing when this new technology is being learned. A new method is provided to allow for quickly converting units from Kelvin for display.

-(double)convertUnitsFromKelvin:(double)kelvinUnits
{
    double adjustedTemp = 0;

    //Kelvin to F
    if([self.unitSegmentedController selectedSegmentIndex] == 0)
    {
        adjustedTemp = ((kelvinUnits-273.15)*1.8)+32;
    }

    //Kelvin to C
    if([self.unitSegmentedController selectedSegmentIndex] == 1)
    {
        adjustedTemp = kelvinUnits-273.15;
    }

    return adjustedTemp;
}

The first method will save the most recent temperature data; it is very similar to the methods that were used to save height and weight information. The method starts with converting the entered temperature from whatever units the user has selected to Kelvin. A new HKUnit is specified and set to kelvinUnit. The HKQuantity is created with the Kelvin temperature value, and the type is set to HKQuantityTypeIdentifierBodyTemperature. The current time is once again used for the data entry and the data is then saved into HealthKit.

-(void)updateMostRecentTemp:(double)temp
{
         double adjustedTemp = 0;

        //F to Kelvin
        if([self.unitSegmentedController selectedSegmentIndex] == 0)
        {
            adjustedTemp = ((temp-32)/1.8)+273.15;
        }

        //C to Kelvin
        if([self.unitSegmentedController selectedSegmentIndex] == 1)
        {
            adjustedTemp = temp+273.15;
        }

        // Save the user's height into HealthKit.
        HKUnit *kelvinUnit = [HKUnit kelvinUnit];

        HKQuantity *tempQuainity = [HKQuantity quantityWithUnit:kelvinUnit
        doubleValue:adjustedTemp];

        HKQuantityType *tempType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];

        NSDate *now = [NSDate date];

        HKQuantitySample *tempSample = [HKQuantitySample quantitySampleWithType:tempType

        quantity:tempQuainity startDate:now endDate:now];

       [self.healthStore saveObject:tempSample withCompletion: ^(BOOL success, NSError *error)
        {
               if (!success)
               {
                        NSLog(@"An error occurred saving temp (%@): %@.", tempSample, error);
                }

                 [self updateTemp];
     }];

}


Note

When a HKQuantitySample is being created, the option to attach metadata is also provided via the call quantitySampleWithType:quantity:startDate:endDate:metadata:. This metadata conforms to a number of predefined keys such as HKMetadataKeyTimeZone, HKMetadataKeyWasTakenInLab, HKMetadataKeyBodyTemperatureSensorLocation, or almost 20 other options. Custom keys can also be created that are specific to the need of each unique app.


Although the app allows for storing only the current body temperature, it also provides the user with insight into historical body temperatures. This process starts in the updateTemp method, which begins in the same fashion as retrieving height and weight information from the preceding section. A new HKQuantityType is created and set to HKQuantityTypeIdentifierBodyTemperature. Apple’s convenience method for returning the most recent entry item is used again. If the call returns data, it is converted from Kelvin into the user’s selected preference and displayed to the interface.

-(void)updateTemp
{
    HKQuantityType *recentTempType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];

    [self.healthStore aapl_mostRecentQuantitySampleOfType:recentTempType predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSError *error)
        {
                if (!mostRecentQuantity)
                {
                        NSLog(@"No temp was found");

                        dispatch_async(dispatch_get_main_queue(), ^{
                               self.recentTempLabel.text = @"Most Recent Temp: None Found";
                        });
                }

                 else
                {
                        HKUnit *kelvinUnit = [HKUnit kelvinUnit];
                        double temp = [mostRecentQuantity doubleValueForUnit:kelvinUnit];

                        dispatch_async(dispatch_get_main_queue(), ^{
                        self.recentTempLabel.text = [NSString stringWithFormat: @"Most Recent Temp: %0.2f", [self convertUnitsFromKelvin:temp]];
                        });
                }
        }];

This result, however, provides only a single entry for the most recent temperature; the app will need access to all the temperature data in order to compile the average as well as the lowest and highest entry points. To do this, a new method is created and added to the Apple extensions file. The heart of this new method is the HKSampleQuery. A new query is created with the passed-in quantityType. The limit is set to 0, which indicates that all records for the specified type should be returned. The results will not be sorted since no sort descriptor is passed in; in addition, no predicate is supplied so items will not be filtered down. The result will return an NSArray of HKQuantitySample objects, each representing a body temperature entry.

-(void)allQuantitySampleOfType:(HKQuantityType *)quantityType predicate:(NSPredicate *)predicate completion: (void (^)(NSArray *, NSError *))completion
{
    HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:quantityType predicate:nil limit:0 sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {

if (!results)
        {
            if (completion)
            {
                completion(nil, error);
            }

            return;
       }

       if (completion)
       {
            completion(results, error);
       }
   }];

      [self executeQuery:query];
}

The existing updateTemp method can now be expanded. Since all the results will be in Kelvin, a new HKUnit is set up specifying that unit type. Some variables are also created to store values for max, min, item count, sum, and the average temperature. The method then loops through the entire result entry array. Each HKQuantitySample object contains a start date, an end date, and a quantity. These are compared for min and max against the other items and each value is stored. Additionally, all entries that occurred within the past 604,800 seconds (7 days) are summed and later averaged. After all the data is processed, the labels are updated on the main thread.

[self.healthStore allQuantitySampleOfType:recentTempType predicate:nil completion:^(NSArray *results, NSError *error)
{
        if (!results)
        {
                NSLog(@"No temp was found");
        }

        else
        {
                HKUnit *kelvinUnit = [HKUnit kelvinUnit];

                double max = 0;
                double min = 1000;

                double sum  = 0;
                int numberOfSamples = 0;
                double averageTemp = 0;

                for(int x = 0; x < [results count]; x++)
                {
                          HKQuantitySample *sample = [results objectAtIndex: x];

                          if([[sample quantity] doubleValueForUnit:kelvinUnit] > max)
                       {
                               max = [[sample quantity] doubleValueForUnit:kelvinUnit];
                        }

                        if([[sample quantity] doubleValueForUnit:kelvinUnit] < min)
                        {
                                min = [[sample quantity] doubleValueForUnit:kelvinUnit];
                       }

                       //7 days' worth of seconds
                       if ([[sample startDate] timeIntervalSinceNow] < 604800.0)
                       {
                                sum += [[sample quantity] doubleValueForUnit:kelvinUnit];
                                numberOfSamples++;
                        }

                }

                averageTemp = sum/numberOfSamples;

                 dispatch_async(dispatch_get_main_queue(), ^{
                      self.highestTempLabel.text = [NSString stringWithFormat: @"Highest Temp: %0.2f", [self convertUnitsFromKelvin:max]];

                      self.lowestTempLabel.text = [NSString stringWithFormat: @"Lowest Temp: %0.2f", [self convertUnitsFromKelvin:min]];

                      self.avgTempLabel.text = [NSString stringWithFormat: @"Average Temp (7 Days): %0.2f", [self convertUnitsFromKelvin: averageTemp]];

             });
         }
     }];

Summary

HealthKit is a fairly large topic, which can encompass a tremendous amount of data; however, the basic principles described in this chapter, such as reading and writing, hold true for even the most complex use cases. Despite the possibility of dozens of apps storing hundreds of data points into HealthKit each day, that data can be easy to parse and work with.

There are more than 70 unique data types in HealthKit today, and it is likely those will continue to expand with each major iOS revision. There is already a large amount of public outcry that certain data is not standard yet. The information provided in this chapter, as in the rest of this book, is designed to provide you with a kick start to development. Although this is not a definitive guide to HealthKit, it should be more than enough to get traction with the technology to create new apps that far exceed anything Apple had in mind when they created this technology.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.125.139