Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Beginning iOS5 Development.pdf
Скачиваний:
7
Добавлен:
09.05.2015
Размер:
15.6 Mб
Скачать

CHAPTER 19: Whee! Gyro and Accelerometer!

647

the accelerometer values won’t change. That’s because the forces bent on moving the phone—in this case, just the force of gravity pulling straight down the z axis—aren’t changing. (In reality, things are a bit fuzzier than that, and the action of your hand bumping the phone will surely trigger a small amount of accelerometer action.) During that same movement, however, the device’s rotation values will change—in particular, the z-axis rotation value. Turning the device clockwise will generate a negative value, and turning it counterclockwise gives a positive value. Stop turning, and the z-axis rotation value will go back to zero.

Rather than registering an absolute rotation value, the gyroscope tells you about changes to the device’s rotation as they happen. You’ll see how this works in this chapter’s first example, coming up shortly.

Core Motion and the Motion Manager

In iOS 4 and up, accelerometer and gyroscope values are accessed using the Core Motion framework. This framework provides, among other things, the CMMotionManager class, which acts as a gateway for all the values describing how the device is being moved by its user. Your application creates an instance of CMMotionManager, and then puts it to use in one of two modes:

It can execute some code for you whenever motion occurs.

It can hang on to a perpetually updated structure that lets you access the latest values at any time.

The latter method is ideal for games and other highly interactive applications that need to be able to poll the device’s current state during each pass through the game loop. We’ll show you how to implement both approaches.

Note that the CMMotionManager class isn’t actually a singleton, but your application should treat it like one. You should create only one of these per app, using the normal alloc and init methods. So, if you need to access the motion manager from several places in your app, you should probably create it in your application delegate and provide access to it from there.

Besides the CMMotionManager class, Core Motion also provides a few other classes, such as CMAccelerometerData and CMGyroData, which are simple containers through which your application can access motion data. We’ll touch on these classes as we get to them.

Event-Based Motion

We mentioned that the motion manager can operate in a mode where it executes some code for you each time the motion data changes. Most other Cocoa Touch classes offer this sort of functionality by letting you connect to a delegate that gets a message when the time comes, but Core Motion does things a little differently.

www.it-ebooks.info

648

CHAPTER 19: Whee! Gyro and Accelerometer!

Since it’s a new framework, available only in iOS 4 and up, Apple decided to let CMMotionManager use another new feature of the iOS 4 SDK: blocks. We’ve already used blocks a couple of times in this book, and now you’re going to see another application of this technique.

Use Xcode to create a new Single View Application project named MotionMonitor, with the Use Storyboard option turned off. This will be a simple app that reads both accelerometer data and gyroscope data (if available) and displays the information on the screen.

NOTE: The applications in this chapter do not function on the simulator because the simulator

has no accelerometer. Aw, shucks.

First, we need to link Core Motion into our app. This is an optional system framework, so we must add it in. Follow the instructions from Chapter 7 for adding the Audio Toolbox framework (in the “Linking in the Audio Toolbox Framework” section), but instead of selecting AudioToolbox.framework, select CoreMotion.framework. (In a nutshell, select the project in the project navigator, select the target and the Build Phases tab, expand the Link Binary with Libraries view, and click the plus button.)

Now, select the BIDViewController.h file, and make the following changes:

#import <UIKit/UIKit.h>

#import <CoreMotion/CoreMotion.h>

@interface BIDViewController : UIViewController

@property (strong, nonatomic) CMMotionManager *motionManager; @property (weak, nonatomic) IBOutlet UILabel *accelerometerLabel; @property (weak, nonatomic) IBOutlet UILabel *gyroscopeLabel;

@end

This provides us with a pointer for accessing the motion manager itself, along with outlets to a pair of labels where we’ll display the information. Nothing much needs to be explained here, so just go ahead and save your changes.

Next, open BIDViewController.xib in Interface Builder. Open the view by selecting its icon in the nib window, and then drag out a Label from the library into the view. Resize the label to make it run from the left blue guideline to the right blue guideline, resize it to be about half the height of the entire view, and then align the top of the label to the top blue guideline.

Now, open the attributes inspector and change the Lines field from 1 to 0. The Lines attribute is used to specify just how many lines of text may appear in the label, and provides a hard upper limit. If you set it to 0, no limit is applied, and the label can contain as many lines as you like.

Next, option-drag the label to create a copy, and align the copy with the blue guidelines in the bottom half of the view.

www.it-ebooks.info

CHAPTER 19: Whee! Gyro and Accelerometer!

649

Now, control-drag from the File’s Owner icon to each of the labels, connecting accelerometerLabel to the upper one and gyroscopeLabel to the lower one.

Finally, double-click each of the labels and delete the existing text.

This simple GUI is complete, so save your work and get ready for some coding.

Next, select BIDViewController.m. Here, add the property synthesizers to the top of the implementation block and the memory management calls to the viewDidUnload method:

#import "BIDViewController.h"

@implementation BIDViewController

@synthesize motionManager; @synthesize accelerometerLabel; @synthesize gyroscopeLabel;

.

.

.

- (void)viewDidUnload

{

[super viewDidUnload];

//Release any retained subviews of the main view.

//e.g. self.myOutlet = nil;

self.motionManager = nil; self.accelerometerLabel = nil; self.gyroscopeLabel = nil;

}

.

.

.

Now comes the interesting part. Fill in the viewDidLoad method with the following content:

- (void)viewDidLoad

{

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib. self.motionManager = [[CMMotionManager alloc] init];

NSOperationQueue *queue = [[NSOperationQueue alloc] init]; if (motionManager.accelerometerAvailable) {

motionManager.accelerometerUpdateInterval = 1.0 / 10.0; [motionManager startAccelerometerUpdatesToQueue:queue withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error){

NSString *labelText; if (error) {

[motionManager stopAccelerometerUpdates]; labelText = [NSString stringWithFormat:

@"Accelerometer encountered error: %@", error];

} else {

labelText = [NSString stringWithFormat: @"Accelerometer\n-----------\nx: %+.2f\ny: %+.2f\nz: %+.2f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z];

www.it-ebooks.info

650 CHAPTER 19: Whee! Gyro and Accelerometer!

}

[accelerometerLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText

waitUntilDone:NO];

}]; } else {

accelerometerLabel.text = @"This device has no accelerometer.";

}

if (motionManager.gyroAvailable) { motionManager.gyroUpdateInterval = 1.0 / 10.0; [motionManager startGyroUpdatesToQueue:queue withHandler: ^(CMGyroData *gyroData, NSError *error) {

NSString *labelText; if (error) {

[motionManager stopGyroUpdates]; labelText = [NSString stringWithFormat:

@"Gyroscope encountered error: %@", error];

} else {

labelText = [NSString stringWithFormat:

@"Gyroscope\n--------\nx: %+.2f\ny: %+.2f\nz: %+.2f", gyroData.rotationRate.x,

gyroData.rotationRate.y,

gyroData.rotationRate.z];

}

[gyroscopeLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText

waitUntilDone:NO];

}]; } else {

gyroscopeLabel.text = @"This device has no gyroscope";

}

}

This method contains all the code we need to fire up the sensors, tell them to report to us every 1/10 second, and update the screen when they do so.

Thanks to the power of blocks, it’s all really simple and cohesive. Instead of putting parts of the functionality in delegate methods, defining behaviors in blocks lets us see a behavior in the same method where it’s being configured. Let’s take this apart a bit. We start off with this:

self.motionManager = [[CMMotionManager alloc] init]; NSOperationQueue *queue = [[NSOperationQueue alloc] init];

This code first creates an instance of CMMotionManager, which we’ll use to watch motion events. Then it creates an operation queue, which is simply a container for a pile of work that needs to be done, as you may recall from Chapter 15.

www.it-ebooks.info

CHAPTER 19: Whee! Gyro and Accelerometer!

651

CAUTION: The motion manager wants to have a queue in which it will put the bits of work to be done, as specified by the blocks you will give it, each time an event occurs. It would be tempting to use the system’s default queue for this purpose, but the documentation for CMMotionManager explicitly warns not to do this! The concern is that the default queue could end up chock-full of these events and have a hard time processing other crucial system events

as a result.

Then we go on to configure the accelerometer. We first check to make sure the device actually has an accelerometer. All handheld iOS devices released so far do have one, but it’s worth checking in case some future device doesn’t. Then we set the time interval we want between updates, specified in seconds. Here, we’re asking for 1/10 second. Note that setting this doesn’t guarantee that we’ll receive updates at precisely that speed. In fact, that setting is really a cap, specifying the best rate the motion manager will be allowed to give us. In reality, it may update less frequently than that.

if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0/10.0;

Next, we tell the motion manager to start reporting accelerometer updates. We pass in the queue where it will put its work and the block that defines the work that will be done each time an update occurs. Remember that a block always starts off with a caret (^), followed by a parentheses-wrapped list of arguments that the block expects to be populated when it’s executed (in this case, the accelerometer data and potentially an error to alert us of trouble), and finishes with a curly brace section containing the code to be executed itself.

[motionManager startAccelerometerUpdatesToQueue:queue withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) {

Then comes the content of the block. It creates a string based on the current accelerometer values, or it generates an error message if there’s a problem. Then it pushes that string value into the accelerometerLabel. Here, we can’t do that directly, since UIKit classes like UILabel usually work well only when accessed from the main thread. Due to the way this code will be executed, from within an NSOperationQueue, we simply don’t know the specific thread in which we’ll be executing. So, we use the performSelectorOnMainThread:withObject:waitUntilDone: method to make the main thread handle this.

Note that the accelerometer values are accessed through the acceleration property of the accelerometerData that was passed into it. The acceleration property is of type CMAcceleration, which is just a simple struct containing three float values. accelerometerData itself is an instance of the CMAccelerometerData class, which is really just a wrapper for CMAcceleration! If you think this seems like an unnecessary profusion of classes and types for simply passing three floats around, well, you’re not alone. Regardless, here’s how to use it:

NSString *labelText; if (error) {

www.it-ebooks.info

652 CHAPTER 19: Whee! Gyro and Accelerometer!

[motionManager stopAccelerometerUpdates]; labelText = [NSString stringWithFormat:

@"Accelerometer encountered error: %@", error];

} else {

labelText = [NSString stringWithFormat: @"Accelerometer\n-----------\nx: %+.2f\ny: %+.2f\nz: %+.2f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z];

}

[accelerometerLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText

waitUntilDone:NO];

Then we finish the block, and complete the square-bracketed method call where we were passing that block in the first place. Finally, we provide a different code path entirely, in case the device doesn’t have an accelerometer. As mentioned earlier, all iOS devices so far have an accelerometer, but who knows what the future holds in store?

}]; } else {

accelerometerLabel.text = @"This device has no accelerometer.";

}

The code for the gyroscope is, as you surely noticed, structurally identical, differing only in the particulars of which methods are called and how reported values are accessed. It’s similar enough that there’s no need to walk you through it here.

Now, build and run your app on whatever iOS device you have, and try it out (see Figure 19–2). As you tilt your device around in different ways, you’ll see how the accelerometer values adjust to each new position, and will hold steady as long as you hold the device steady.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]