Ad

Our DNA is written in Swift
Jump

M7 Pedometer

Apple’s iPhone 5S features a dedicated motion coprocessor, dubbed the “M7”. Probably because the M stands for Movement/Motion similar to the A in “A7” standing for … um, Apple. This coprocessor collects and analyzes data from multiple iPhone sensors and determines two interesting pieces of information:

  • the motion activity state of its user (car, walking, running, standing still) ad hoc and history
  • the number of steps taken ad hoc and history

Let’s build a functional pedometer app so that we can learn about and explore the latter.

Both above mentioned features come with their own separate APIs. This might be a bit confusing initially. You cannot simply get the motion state at a given time and then inquire about the steps taken in this state if it was “walking”. Motion activity state is managed by CMMotionManager instances whereas steps are observable via CMStepCounter. Two distinct classes with similar API, but separate functionality. In this tutorial I’ll only be looking at the step counter.

Let’s cleanly separate the view controller and step counter in our code. I know, the temptation to put everything into the view controller is big. But let’s do it like this, it’s cleaner.

Pedometer MVC

To use Core Motion APIs in our app we need to add the CoreMotion.framework to be linked with our app target. I also like to add the all-in header to the PCH file.

There are two main things you can do with CMStepCounter. You can query the number of steps between two dates and you can get live updates every n steps. Both are block-based and execute the handler block on an NSOperationQueue of your choice. In contrast to other similar APIs you cannot specify a nil queue. If you are lazy you can use [NSOperationQueue mainQueue] in its stead, but we will use a dedicated queue.

Step Model Controller

Create a new NSObject subclass and name it DTStepModelController. This is the header. Note that I removed the superfluous import which duplicates an import already found in the PCH file.

@interface DTStepModelController : NSObject
 
@property (readonly) NSInteger stepsToday;
 
@end

The implementation’s init might seem a bit much at first, but bear with me, it will soon become clear to you.

@interface DTStepModelController ()
 
@property (assign) NSInteger stepsToday;
 
@end
 
@implementation DTStepModelController
{
   CMStepCounter *_stepCounter;
   NSInteger _stepsToday;
   NSInteger _stepsAtBeginOfLiveCounting;
   BOOL _isLiveCounting;
   NSOperationQueue *_stepQueue;
}
 
- (instancetype)init
{
   self = [super init];
 
   if (self)
   {
      _stepCounter = [[CMStepCounter alloc] init];
      self.stepsToday = -1;
 
      NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
 
      // subscribe to relevant notifications
      [noteCenter addObserver:self selector:@selector(timeChangedSignificantly:) 
                         name:UIApplicationSignificantTimeChangeNotification object:nil];
      [noteCenter addObserver:self selector:@selector(willEnterForeground:)
                         name:UIApplicationWillEnterForegroundNotification
                       object:nil];
      [noteCenter addObserver:self selector:@selector(didEnterBackground:)
                         name:UIApplicationDidEnterBackgroundNotification
                       object:nil];
 
      // queue for step count updating
      _stepQueue = [[NSOperationQueue alloc] init];
      _stepQueue.maxConcurrentOperationCount = 1;
 
      // start counting
      [self _updateStepsTodayFromHistoryLive:YES];
   }
 
   return self;
}

The _stepCounter IVAR is the reference to the CMStepCounter instance we will be using. _stepsToday is the number of steps since midnight. If steps are not available then this value will be -1. This can happen if the device does not support step counting, for lack of an M7, or if the user removes authorization for the app to access the M7.

We subscribe to 3 app notifications which are relevant to our controller. We want to know if there is a significant time change (like from one day to the next) and if the app moves between foreground and background states. A dedicated _stepQueue serializes the handler block executions for us. The last statement in the init calls the central update function.

Of course we clean up notification subscriptions in the dealloc.

- (void)dealloc
{
   [[NSNotificationCenter defaultCenter] removeObserver:self];
}

The key method follows. This queries the number of steps from beginning of day until the current time. If the parameter is YES then it also starts live step count updates.

// queries the CMStepCounter history from midnight until now
- (void)_updateStepsTodayFromHistoryLive:(BOOL)startLiveCounting
{
   if (![CMStepCounter isStepCountingAvailable])
   {
      NSLog(@"Step counting not available on this device");
 
      self.stepsToday = -1;
      return;
   }
 
   NSDate *now = [NSDate date];
 
   NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
   NSDateComponents *components = [calendar components:NSYearCalendarUnit
                                   | NSMonthCalendarUnit
                                   | NSDayCalendarUnit
                                              fromDate:now];
 
   NSDate *beginOfDay = [calendar dateFromComponents:components];
 
   [_stepCounter queryStepCountStartingFrom:beginOfDay
                                         to:now
                                    toQueue:_stepQueue
                                withHandler:^(NSInteger numberOfSteps, 
                                              NSError *error) {
 
                                   if (error)
                                   {
                                      // note: CMErrorDomain, code 105 means not authorized
                                      NSLog(@"%@", [error localizedDescription]);
                                      self.stepsToday = -1;
                                   }
                                   else
                                   {
                                      self.stepsToday = numberOfSteps;
 
                                      if (startLiveCounting)
                                      {
                                         [self _startLiveCounting];
                                      }
                                   }
                                }];
}

At the top you see that stepsToday is set to -1 via the property. The property is defined as atomic and used for setting because this is by default KVO-observable. Meaning that the default implementation of properties contains sending of the willChangeValueForKey: and didChangeValueForKey: events for each change. Less code to write for us.

The previously initialized CMStepCounter is queried for the number of steps between these two dates and once this is done the handler block will be executed on our private operation queue. If there is an error then we assume that step counting is not available at all and set the number of steps to -1. Otherwise we update the property. Then if the parameter was YES we also start live updating.

Next up are two helper methods used later on that start and stop the live updating. An IVAR _isLiveCounting keeps track of the state so that we don’t stop or start multiple times in succession.

- (void)_startLiveCounting
{
   if (_isLiveCounting)
   {
      return;
   }
 
   _isLiveCounting = YES;
   _stepsAtBeginOfLiveCounting = self.stepsToday;
   [_stepCounter startStepCountingUpdatesToQueue:_stepQueue
                                        updateOn:1
                                     withHandler:^(NSInteger numberOfSteps, 
                                                   NSDate *timestamp, 
                                                   NSError *error) {
                                        self.stepsToday = _stepsAtBeginOfLiveCounting
                                                          + numberOfSteps;
                                     }];
 
   NSLog(@"Started live step counting");
}
 
- (void)_stopLiveCounting
{
   if (!_isLiveCounting)
   {
      return;
   }
 
   [_stepCounter stopStepCountingUpdates];
   _isLiveCounting = NO;
 
   NSLog(@"Stopped live step counting");
}

You can see that we want to know about each step because we set the updateOn to one. In practice you don’t get every single step, but the M7 might deliver an update every few steps. Sometimes every second, sometimes less.

The reason for the _stepsAtBeginOfLiveCounting IVAR is that step updates are always relative to the number of steps count at the beginning of the live updates. Since we want the number of steps for the entire day – and not just from when the app was launched – we have to sum these two values.

Ok, finally the handler for the 3 notifications we care about.

- (void)timeChangedSignificantly:(NSNotification *)notification
{
   [self _stopLiveCounting];
 
   [self _updateStepsTodayFromHistoryLive:YES];
}
 
- (void)willEnterForeground:(NSNotification *)notification
{
   [self _updateStepsTodayFromHistoryLive:YES];
}
 
- (void)didEnterBackground:(NSNotification *)notification
{
   [self _stopLiveCounting];
}

If the date changes you want steps to begin at 0 regardless if the app is in background or not. So we stop live updates and then restart them via the special function described above.

The main reason why the step model controller should pause step counting while in background is founded in the way the updates are delivered in that case. The updates are queued up are delivered all at once when the app resumes foreground state. This can make the app sluggish when returning from background. There are prominent pedometer apps which make this mistake.

Hooking up the UI

Now that we have a fully functional step counter model controller connecting some UI with it fairly straightforward. In the app’s story board we add a large label to take on our step count. This we hook up to an IBOutlet so that we can set it from the observed step count. The label font size is allowed to shrink for text that does not fit the space.

Simple Pedometer UI

In the sample app I also disabled interface rotation to make things easier. Now for the KVO.

@implementation ViewController
{
   DTStepModelController *_stepModel;
}
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
   _stepModel = [[DTStepModelController alloc] init];
 
   [_stepModel addObserver:self forKeyPath:@"stepsToday" 
                   options:NSKeyValueObservingOptionNew context:NULL];
 
   [self _updateSteps:_stepModel.stepsToday];
}
 
- (void)dealloc
{
   [_stepModel removeObserver:self forKeyPath:@"stepsToday"];
}

We create an instance of the step model controller in the viewDidLoad method. KVO is installed for the stepsToday property. An initial update is performed. The dealloc removes the KVO. This is probably never called, but better to have this in there to show that we care about the symmetry between adding an observer and removing it.

The KVO observer is extremely simple as well. All it does is update the steps label, like the viewDidLoad does.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
   [self _updateSteps:_stepModel.stepsToday];
}

The update method looks like this:

- (void)_updateSteps:(NSInteger)steps
{
   // force main queue for UIKit
   dispatch_async(dispatch_get_main_queue(), ^{
      if (steps>=0)
      {
         self.stepsLabel.text = [NSString stringWithFormat:@"%ld",
                                 (long)steps];
         self.stepsLabel.textColor = [UIColor colorWithRed:0
                                                     green:0.8
                                                      blue:0
                                                     alpha:1];
      }
      else
      {
         self.stepsLabel.text = @"Not available";
         self.stepsLabel.textColor = [UIColor redColor];
      }
   });
}

If the steps count is valid then the label is set to a darker green and updated with the number. If not, then the label turns red and shows “Not available”. Note the dispatch to the main queue which is necessary because of our private step counting queue. You never want to update UIKit objects on any other queue than the main queue.

Finished Pedometer Sample

There is one more detail which we didn’t touch opon so far: M7 authorization. The first time an app queries the M7 the user is asked whether he wants to allow access. If this is not authorized then all queries return an error code 105 in CMErrorDomain. This is handled by the step model controller by setting the number of steps to -1 regardless of the error. If the app is background and the user switches the authorization switch in the private settings iOS kills the app. Upon relaunch the update is retried resulting in the “Not available” if not authorized and the step number if all is ok.

 

Conclusion

Building a step counter – aka Pedometer – using the Apple M7 is fairly easy but there are a few gotchas to consider. Authorization status (can be off), devices without M7, background state and updates being relative to the number of steps at the start of the updates.

But this tutorial shows that if you wrap it all into a dedicated controller class you can keep your UI code clean and simple. Full source code for this example is on my Examples GitHub repository.

PS: If you want to support me please buy a copy of my new book Barcodes with iOS 7. I promise you will never regret it because the tutorials in there are just as informative and cool as this one. 🙂


Categories: Recipes

2 Comments »

  1. Very nice article! However, I have an issue with your statement, “The updates are queued up are delivered all at once when the app resumes foreground state. This can make the app sluggish when returning from background.”. The CMStepCounter documentation for startStepCountingUpdatesToQueue:updateOn:withHandler: says “The handler block is executed on a best effort basis each time the step count threshold is exceeded. If your app is suspended when the threshold is exceeded, the block is not executed. When your app resumes, the block is not executed until the threshold is exceeded again.” My understanding of this is that updates don’t queue up when the app goes to the background.
    I tested this (in my own implementation) and observed that the block executed only once after returning to the foreground.
    If I’m missing something, please do comment.

  2. Great project. Its working perfectly for me, although I am trying to modify it to track steps only during certain time intervals when an event occurs. Ideally I would have 2 sets of data, 1 of all steps (which works as shown) and another that only tracks a subset of steps while a boolean flag is set to YES. Should I create a duplicate of DTStepModelController.h/.m like DTStepModelController2 ? Or within DTStepModelController can I initiate a second CMPedometer counter and somehow assign it to a different background queue?

    Any ideas much appreciated.