A lot of cool technologies have evolved on the mobile computing platform in recent years. One of the more interesting hardware developments is the ability to detect and respond to physical motion. Most mobile devices today deploy a host of onboard instruments for this very purpose.

Although individual iOS device models might only support some hardware instruments, information from any available instrument can be accessed using the Core Motion framework. The post will walk you through the required classes and methods as included in the example project below titled CoreMotionAccelerometer.

The Hardware

As mentioned already, Apple devices typically come with a collection of onboard instruments suited to detect motion - namely, the accelerometer, the magnetometer, and the gyroscope. Much could be written about each of these instruments, but for our purposes I want to focus solely on the accelerometer and demonstrate how it is managed through Core Motion in iOS.

According to Apple, the accelerometer is used to measure device "acceleration along the three spatial axes at a moment of time." That means that we have available to us a data set that describes the hardware orientation (e.g. vertical and horizontal positions) at any given time. Our task then is to discover how to access that data and then do something useful with it.

Core Motion

As the name suggests, the Core Motion framework is a collection of related classes that provide a way for your iOS application to receive motion data. Part of Core Motion is the CMMotionManager class. You can think of the CMMotionManager class as the gateway to motion services provided by iOS.

Once created (i.e. instantiated) in your app, the CMMotionManager object provides accelerometer data, rotation-rate data, magnetometer data, and other device-motion data such as attitude.

Practical Example

Let's start with a blank Xcode project and add the required parts to detect and process motion data. Specifically, we will be working with device accelerometer data. Our finished example app will use the data to animate a view (a small colored square) when the user moves the device around in their hand.

Create The Project

In XCode, create a new "Single View Application" project and be sure to check the ARC option.

Add a Subview

Next we will add a subview to our ViewController. The added subview will be animated in relation to the device as the user moves it.

To add our subview, select the ViewController.xib file in the Xcode project window and drag a view from the object library and drop it onto the ViewController view.

After adding the subview, select and resize it to 100 x 100, then change the background color of the subview to something other than white.

Finally, we will need to connect the subview to our ViewController.

To connect the subview, we will use the Assistant editor. With the subview selected, click the Assistant editor button:

Now control-click the subview and drag a connection into the ViewController.h file, then release:

Upon release, a connection dialog will appear. Select the "Outlet" connection type and name the connection "movingView":

Now we are ready to put Core Motion to work.

Add Core Motion

In our new project we will need to add the Core Motion framework. In the file Navigator pane of Xcode, select your Xcode project icon, then click the Summary tab.

Scroll down to the "Linked Frameworks and Libraries" list and click the "+" icon button to add a new framework.

Select and add "CoreMotion.framework" from the presented list.

Next, we will need to let our classes know about our Core Motion framework, so in the AppDelegate.h file, add:

#import 

In the same way, you will also need to add

#import 

to your ViewController.m file.

Add CMMotionManager

Now that we've added the Core Motion framework to our project and made the required imports, we will need to create a CMMotionManager instance to handle our Core Motion data for us.

Note: The CMMotionManager object should only be created once (i.e. don't create multiple instances in a single app). Creating more than a single CMMotionManager instance can mess up your motion data rates and result in erroneous calculations.

To create our CMMotionManager object, we must first declare it.

Your AppDelegate.h file should look like this:

#import 
 
#import 
 
@class ViewController;
 
@interface AppDelegate : UIResponder {
 
CMMotionManager *motionManager;
 
}
 
@property (readonly) CMMotionManager *motionManager;
 
@property (strong, nonatomic) UIWindow *window;
 
@property (strong, nonatomic) ViewController *viewController;
 
@end

Next, add a getter method to instantiate the CMMotionManager object in the AppDelegate.m file:

- (CMMotionManager *)motionManager
 {
 if (!motionManager) motionManager = [[CMMotionManager alloc] init];
 
 return motionManager;
 }

Finally, we need to add a getter method to the ViewController.m file as well to reference our CMMotionManager object in the AppDelegate implementation.

Add this to ViewController.m:

- (CMMotionManager *)motionManager
 {
 CMMotionManager *motionManager = nil;
 
 id appDelegate = [UIApplication sharedApplication].delegate;
 
 if ([appDelegate respondsToSelector:@selector(motionManager)]) {
 motionManager = [appDelegate motionManager];
 }
 
 return motionManager;
 }

Working with CMMotionManager

CMMotionManager works with an NSOperationQueue and a block object to collect motion data in a threaded context. If that sounds a little intimidating don't worry - it's pretty easy to do.

To begin collecting motion data we will need a start method that we can call when we are ready.

The CMMotionManager provides the startAccelerometerUpdatesToQueue:withHandler method to start Core Motion data collecting. This method uses a threaded context by implementing an NSOperationQueue and an execution block (called a handler) that conforms to the CMAcceleromoterHandler format.

Our start method will also handle the animation since it's a simple task for our purposes. In a more complex project you might want to create a separate method to handle animation after the start method is called.

Let's add this method to the ViewController.m file:

- (void)startMyMotionDetect
 {
 
 __block float stepMoveFactor = 15;
 
 [self.motionManager 
 startAccelerometerUpdatesToQueue:[[<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSOperationQueue_Class/">NSOperationQueue</a> alloc] init]
 withHandler:^(CMAccelerometerData *data, <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/">NSError</a> *error)
 {
 
 dispatch_async(dispatch_get_main_queue(),
 ^{
 
 CGRect rect = self.movingView.frame;
 
 float movetoX = rect.origin.x + (data.acceleration.x * stepMoveFactor);
 float maxX = self.view.frame.size.width - rect.size.width;
 
 float movetoY = (rect.origin.y + rect.size.height) 
 - (data.acceleration.y * stepMoveFactor);
 
 float maxY = self.view.frame.size.height;
 
 if ( movetoX > 0 && movetoX < maxx="">) {
 rect.origin.x += (data.acceleration.x * stepMoveFactor);
 };
 
 if ( movetoY > 0 && movetoY < maxy="">) {
 rect.origin.y -= (data.acceleration.y * stepMoveFactor);
 };
 
 [UIView animateWithDuration:0 delay:0 
 options:UIViewAnimationCurveEaseInOut 
 animations:
 ^{
 self.movingView.frame = rect;
 }
 completion:nil
 ];
 
 }
 );
 }
 ];
 
 }

Here we've done a few things:

First, we've set up the startAccelerometerUpdatesToQueue method by providing an NSOperationQueue and an execution block (here called a handler) that conforms to CMAccelerometerData.

Next, after the NSOerationQueue runs it returns two arguments (data and error) to our handler. These arguments are then passed to another block that starts a thread to handle our animation.

Our animation math calculates where and how far to move our subview using the accelerometer data, and then applies the calculations to the subview frame rect in the animation block.

Finishing it Out

Next, we need to add some code to tell our app when to start collecting motion data. Since we want it to collect that data the whole time our view is showing, we will add this to the viewDidAppear protocol method in the ViewController.m file:

- (void)viewDidAppear:(BOOL)animated 
 {
 
 [super viewDidAppear:animated];
 
 [self startMyMotionDetect];
 
 }

Finally, add this to the viewDidDisappear protocol method in the ViewController.m file to stop collecting Core Motion data once the view is gone:

- (void)viewDidDisappear:(BOOL)animated
 {
 
 [super viewDidDisappear:animated];
 
 [self.motionManager stopAccelerometerUpdates];
 
 }

Note that although we've not done it here in our example, the frequency of the updates can be set by assigning a value (in seconds) to the AccelerometerUpdateInterval property of our CMMotionManager object.

Also, you should always check the device your app is running on for hardware that supports Core Motion. This is done by checking the accelerometerAvailable, gyroAvailable, and deviceMotionAvailable properties of the CMMotionManager object before attempting to use the related class objects.

So that's it. Now we can run our app (on an iPhone or iPad, not in the simulator) and Core Motion will provide updated position data. As the data is collected, our animation block moves the subview in relation to the motion of the device.

Summary

Core Motion is a powerful framework that provides easy access to hardware instruments - including the accelerometer, magnetometer, and gyroscope. These onboard tools provide a great way to interact with your users beyond the touch screen. You will want to be familiar with NSOperationQueues and blocks as the continuous nature of motion data is best managed using those classes.

Keep in Mind

Core Motion data is always provided as radians. If you want degrees you will need to convert the data using the following formulas:

CGFloat DegreesToRadians(CGFloat degrees) {
 return degrees * M_PI / 180;
 };
CGFloat RadiansToDegrees(CGFloat radians) {
 return radians * 180 / M_PI;
 };

More:

Apple Core Motion Framework Reference

Using Blocks