- •Contents at a Glance
- •About the Authors
- •About the Technical Reviewer
- •Acknowledgments
- •Preface
- •What This Book Is
- •What You Need
- •Developer Options
- •What You Need to Know
- •What’s Different About Coding for iOS?
- •Only One Active Application
- •Only One Window
- •Limited Access
- •Limited Response Time
- •Limited Screen Size
- •Limited System Resources
- •No Garbage Collection, but…
- •Some New Stuff
- •A Different Approach
- •What’s in This Book
- •What’s New in This Update?
- •Are You Ready?
- •Setting Up Your Project in Xcode
- •The Xcode Workspace Window
- •The Toolbar
- •The Navigator View
- •The Jump Bar
- •The Utility Pane
- •Interface Builder
- •New Compiler and Debugger
- •A Closer Look at Our Project
- •Introducing Xcode’s Interface Builder
- •What’s in the Nib File?
- •The Library
- •Adding a Label to the View
- •Changing Attributes
- •Some iPhone Polish—Finishing Touches
- •Bring It on Home
- •The Model-View-Controller Paradigm
- •Creating Our Project
- •Looking at the View Controller
- •Understanding Outlets and Actions
- •Outlets
- •Actions
- •Cleaning Up the View Controller
- •Designing the User Interface
- •Adding the Buttons and Action Method
- •Adding the Label and Outlet
- •Writing the Action Method
- •Trying It Out
- •Looking at the Application Delegate
- •Bring It on Home
- •A Screen Full of Controls
- •Active, Static, and Passive Controls
- •Creating the Application
- •Implementing the Image View and Text Fields
- •Adding the Image View
- •Resizing the Image View
- •Setting View Attributes
- •The Mode Attribute
- •Interaction Checkboxes
- •The Alpha Value
- •Background
- •Drawing Checkboxes
- •Stretching
- •Adding the Text Fields
- •Text Field Inspector Settings
- •Setting the Attributes for the Second Text Field
- •Creating and Connecting Outlets
- •Closing the Keyboard
- •Closing the Keyboard When Done Is Tapped
- •Touching the Background to Close the Keyboard
- •Adding the Slider and Label
- •Creating and Connecting the Actions and Outlets
- •Implementing the Action Method
- •Adding Two Labeled Switches
- •Connecting and Creating Outlets and Actions
- •Implementing the Switch Actions
- •Adding the Button
- •Connecting and Creating the Button Outlets and Actions
- •Implementing the Segmented Control Action
- •Implementing the Action Sheet and Alert
- •Conforming to the Action Sheet Delegate Method
- •Showing the Action Sheet
- •Spiffing Up the Button
- •Using the viewDidLoad Method
- •Control States
- •Stretchable Images
- •Crossing the Finish Line
- •The Mechanics of Autorotation
- •Points, Pixels, and the Retina Display
- •Autorotation Approaches
- •Handling Rotation Using Autosize Attributes
- •Configuring Supported Orientations
- •Specifying Rotation Support
- •Designing an Interface with Autosize Attributes
- •Using the Size Inspector’s Autosize Attributes
- •Setting the Buttons’ Autosize Attributes
- •Restructuring a View When Rotated
- •Creating and Connecting Outlets
- •Moving the Buttons on Rotation
- •Swapping Views
- •Designing the Two Views
- •Implementing the Swap
- •Changing Outlet Collections
- •Rotating Out of Here
- •Common Types of Multiview Apps
- •The Architecture of a Multiview Application
- •The Root Controller
- •Anatomy of a Content View
- •Building View Switcher
- •Creating Our View Controller and Nib Files
- •Modifying the App Delegate
- •Modifying BIDSwitchViewController.h
- •Adding a View Controller
- •Building a View with a Toolbar
- •Writing the Root View Controller
- •Implementing the Content Views
- •Animating the Transition
- •Switching Off
- •The Pickers Application
- •Delegates and Data Sources
- •Setting Up the Tab Bar Framework
- •Creating the Files
- •Adding the Root View Controller
- •Creating TabBarController.xib
- •The Initial Test Run
- •Implementing the Date Picker
- •Implementing the Single-Component Picker
- •Declaring Outlets and Actions
- •Building the View
- •Implementing the Controller As a Data Source and Delegate
- •Implementing a Multicomponent Picker
- •Declaring Outlets and Actions
- •Building the View
- •Implementing the Controller
- •Implementing Dependent Components
- •Creating a Simple Game with a Custom Picker
- •Writing the Controller Header File
- •Building the View
- •Adding Image Resources
- •Implementing the Controller
- •The spin Method
- •The viewDidLoad Method
- •Final Details
- •Linking in the Audio Toolbox Framework
- •Final Spin
- •Table View Basics
- •Table Views and Table View Cells
- •Grouped and Plain Tables
- •Implementing a Simple Table
- •Designing the View
- •Writing the Controller
- •Adding an Image
- •Using Table View Cell Styles
- •Setting the Indent Level
- •Handling Row Selection
- •Changing the Font Size and Row Height
- •Customizing Table View Cells
- •Adding Subviews to the Table View Cell
- •Creating a UITableViewCell Subclass
- •Adding New Cells
- •Implementing the Controller’s Code
- •Loading a UITableViewCell from a Nib
- •Designing the Table View Cell in Interface Builder
- •Using the New Table View Cell
- •Grouped and Indexed Sections
- •Building the View
- •Importing the Data
- •Implementing the Controller
- •Adding an Index
- •Implementing a Search Bar
- •Rethinking the Design
- •A Deep Mutable Copy
- •Updating the Controller Header File
- •Modifying the View
- •Modifying the Controller Implementation
- •Copying Data from allNames
- •Implementing the Search
- •Changes to viewDidLoad
- •Changes to Data Source Methods
- •Adding a Table View Delegate Method
- •Adding Search Bar Delegate Methods
- •Adding a Magnifying Glass to the Index
- •Adding the Special Value to the Keys Array
- •Suppressing the Section Header
- •Telling the Table View What to Do
- •Putting It All on the Table
- •Navigation Controller Basics
- •Stacky Goodness
- •A Stack of Controllers
- •Nav, a Hierarchical Application in Six Parts
- •Meet the Subcontrollers
- •The Disclosure Button View
- •The Checklist View
- •The Rows Control View
- •The Movable Rows View
- •The Deletable Rows View
- •The Editable Detail View
- •The Nav Application’s Skeleton
- •Creating the Top-Level View Controller
- •Setting Up the Navigation Controller
- •Adding the Images to the Project
- •First Subcontroller: The Disclosure Button View
- •Creating the Detail View
- •Modifying the Disclosure Button Controller
- •Adding a Disclosure Button Controller Instance
- •Second Subcontroller: The Checklist
- •Creating the Checklist View
- •Adding a Checklist Controller Instance
- •Third Subcontroller: Controls on Table Rows
- •Creating the Row Controls View
- •Adding a Rows Control Controller Instance
- •Fourth Subcontroller: Movable Rows
- •Creating the Movable Row View
- •Adding a Move Me Controller Instance
- •Fifth Subcontroller: Deletable Rows
- •Creating the Deletable Rows View
- •Adding a Delete Me Controller Instance
- •Sixth Subcontroller: An Editable Detail Pane
- •Creating the Data Model Object
- •Creating the Detail View List Controller
- •Creating the Detail View Controller
- •Adding an Editable Detail View Controller Instance
- •But There’s One More Thing. . .
- •Breaking the Tape
- •Creating a Simple Storyboard
- •Dynamic Prototype Cells
- •Dynamic Table Content, Storyboard-Style
- •Editing Prototype Cells
- •Good Old Table View Data Source
- •Will It Load?
- •Static Cells
- •Going Static
- •So Long, Good Old Table View Data Source
- •You Say Segue, I Say Segue
- •Creating Segue Navigator
- •Filling the Blank Slate
- •First Transition
- •A Slightly More Useful Task List
- •Viewing Task Details
- •Make More Segues, Please
- •Passing a Task from the List
- •Handling Task Details
- •Passing Back Details
- •Making the List Receive the Details
- •If Only We Could End with a Smooth Transition
- •Split Views and Popovers
- •Creating a SplitView Project
- •The Storyboard Defines the Structure
- •The Code Defines the Functionality
- •The App Delegate
- •The Master View Controller
- •The Detail View Controller
- •Here Come the Presidents
- •Creating Your Own Popover
- •iPad Wrap-Up
- •Getting to Know Your Settings Bundle
- •The AppSettings Application
- •Creating the Project
- •Working with the Settings Bundle
- •Adding a Settings Bundle to Our Project
- •Setting Up the Property List
- •Adding a Text Field Setting
- •Adding an Application Icon
- •Adding a Secure Text Field Setting
- •Adding a Multivalue Field
- •Adding a Toggle Switch Setting
- •Adding the Slider Setting
- •Adding Icons to the Settings Bundle
- •Adding a Child Settings View
- •Reading Settings in Our Application
- •Retrieving User Settings
- •Creating the Main View
- •Updating the Main View Controller
- •Registering Default Values
- •Changing Defaults from Our Application
- •Keeping It Real
- •Beam Me Up, Scotty
- •Your Application’s Sandbox
- •Getting the Documents Directory
- •Getting the tmp Directory
- •File-Saving Strategies
- •Single-File Persistence
- •Multiple-File Persistence
- •Using Property Lists
- •Property List Serialization
- •The First Version of the Persistence Application
- •Creating the Persistence Project
- •Designing the Persistence Application View
- •Editing the Persistence Classes
- •Archiving Model Objects
- •Conforming to NSCoding
- •Implementing NSCopying
- •Archiving and Unarchiving Data Objects
- •The Archiving Application
- •Implementing the BIDFourLines Class
- •Implementing the BIDViewController Class
- •Using iOS’s Embedded SQLite3
- •Creating or Opening the Database
- •Using Bind Variables
- •The SQLite3 Application
- •Linking to the SQLite3 Library
- •Modifying the Persistence View Controller
- •Using Core Data
- •Entities and Managed Objects
- •Key-Value Coding
- •Putting It All in Context
- •Creating New Managed Objects
- •Retrieving Managed Objects
- •The Core Data Application
- •Designing the Data Model
- •Creating the Persistence View and Controller
- •Persistence Rewarded
- •Managing Document Storage with UIDocument
- •Building TinyPix
- •Creating BIDTinyPixDocument
- •Code Master
- •Initial Storyboarding
- •Creating BIDTinyPixView
- •Storyboard Detailing
- •Adding iCloud Support
- •Creating a Provisioning Profile
- •Enabling iCloud Entitlements
- •How to Query
- •Save Where?
- •Storing Preferences on iCloud
- •What We Didn’t Cover
- •Grand Central Dispatch
- •Introducing SlowWorker
- •Threading Basics
- •Units of Work
- •GCD: Low-Level Queueing
- •Becoming a Blockhead
- •Improving SlowWorker
- •Don’t Forget That Main Thread
- •Giving Some Feedback
- •Concurrent Blocks
- •Background Processing
- •Application Life Cycle
- •State-Change Notifications
- •Creating State Lab
- •Exploring Execution States
- •Making Use of Execution State Changes
- •Handling the Inactive State
- •Handling the Background State
- •Removing Resources When Entering the Background
- •Saving State When Entering the Background
- •A Brief Journey to Yesteryear
- •Back to the Background
- •Requesting More Backgrounding Time
- •Grand Central Dispatch, Over and Out
- •Two Views of a Graphical World
- •The Quartz 2D Approach to Drawing
- •Quartz 2D’s Graphics Contexts
- •The Coordinate System
- •Specifying Colors
- •A Bit of Color Theory for Your iOS Device’s Display
- •Other Color Models
- •Color Convenience Methods
- •Drawing Images in Context
- •Drawing Shapes: Polygons, Lines, and Curves
- •The QuartzFun Application
- •Setting Up the QuartzFun Application
- •Creating a Random Color
- •Defining Application Constants
- •Implementing the QuartzFunView Skeleton
- •Creating and Connecting Outlets and Actions
- •Implementing the Action Methods
- •Adding Quartz 2D Drawing Code
- •Drawing the Line
- •Drawing the Rectangle and Ellipse
- •Drawing the Image
- •Optimizing the QuartzFun Application
- •The GLFun Application
- •Setting Up the GLFun Application
- •Creating BIDGLFunView
- •Updating BIDViewController
- •Updating the Nib
- •Finishing GLFun
- •Drawing to a Close
- •Multitouch Terminology
- •The Responder Chain
- •Responding to Events
- •Forwarding an Event: Keeping the Responder Chain Alive
- •The Multitouch Architecture
- •The Four Touch Notification Methods
- •The TouchExplorer Application
- •The Swipes Application
- •Automatic Gesture Recognition
- •Implementing Multiple Swipes
- •Detecting Multiple Taps
- •Detecting Pinches
- •Defining Custom Gestures
- •The CheckPlease Application
- •The CheckPlease Touch Methods
- •Garçon? Check, Please!
- •The Location Manager
- •Setting the Desired Accuracy
- •Setting the Distance Filter
- •Starting the Location Manager
- •Using the Location Manager Wisely
- •The Location Manager Delegate
- •Getting Location Updates
- •Getting Latitude and Longitude Using CLLocation
- •Error Notifications
- •Trying Out Core Location
- •Updating Location Manager
- •Determining Distance Traveled
- •Wherever You Go, There You Are
- •Accelerometer Physics
- •Don’t Forget Rotation
- •Core Motion and the Motion Manager
- •Event-Based Motion
- •Proactive Motion Access
- •Accelerometer Results
- •Detecting Shakes
- •Baked-In Shaking
- •Shake and Break
- •Accelerometer As Directional Controller
- •Rolling Marbles
- •Writing the Ball View
- •Calculating Ball Movement
- •Rolling On
- •Using the Image Picker and UIImagePickerController
- •Implementing the Image Picker Controller Delegate
- •Road Testing the Camera and Library
- •Designing the Interface
- •Implementing the Camera View Controller
- •It’s a Snap!
- •Localization Architecture
- •Strings Files
- •What’s in a Strings File?
- •The Localized String Macro
- •Real-World iOS: Localizing Your Application
- •Setting Up LocalizeMe
- •Trying Out LocalizeMe
- •Localizing the Nib
- •Localizing an Image
- •Generating and Localizing a Strings File
- •Localizing the App Display Name
- •Auf Wiedersehen
- •Apple’s Documentation
- •Mailing Lists
- •Discussion Forums
- •Web Sites
- •Blogs
- •Conferences
- •Follow the Authors
- •Farewell
- •Index
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