- Accessing Basic Device Information
- Adding Device Capability Restrictions
- Recipe: Checking Device Proximity and Battery States
- Recipe: Recovering Additional Device Information
- Recipe: Using Acceleration to Locate "Up"
- Working with Basic Orientation
- Retrieving the Current Accelerometer Angle Synchronously
- Recipe: Using Acceleration to Move Onscreen Objects
- Recipe: Accelerometer-Based Scroll View
- Recipe: Core Motion Basics
- Recipe: Retrieving and Using Device Attitude
- Detecting Shakes Using Motion Events
- Recipe: Using External Screens
- Tracking Users
- One More Thing: Checking for Available Disk Space
- Summary
Retrieving the Current Accelerometer Angle Synchronously
At times you may want to query the accelerometer without setting yourself up as a full delegate. The following methods, which are meant for use within a UIDevice category, enable you to synchronously return the current device angle along the x/y plane—the front face plane of the iOS device. Accomplish this by entering a new run loop, wait for an accelerometer event, retrieve the current angle from that callback, and then leave the run loop to return that angle:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { float xx = acceleration.x; float yy = -acceleration.y; device_angle = M_PI / 2.0f - atan2(yy, xx); if (device_angle > M_PI) device_angle -= 2 * M_PI; CFRunLoopStop(CFRunLoopGetCurrent()); } - (float) orientationAngle { // Supercede current delegate id priorDelegate = [UIAccelerometer sharedAccelerometer].delegate; [UIAccelerometer sharedAccelerometer].delegate = self; // Wait for a reading CFRunLoopRun(); // Restore delegate [UIAccelerometer sharedAccelerometer].delegate = priorDelegate; return device_angle; }
This is not an approach to use for continuous polling—use the callbacks directly for that. But for an occasional angle query, these methods provide simple and direct access to the current screen angle.
Calculating Orientation from the Accelerometer
The UIDevice class does not report a proper orientation when applications are first launched. It updates the orientation only after the device has moved into a new position or UIViewController methods kick in.
An application launched in portrait orientation may not read as “portrait” until the user moves the device out of and then back into the proper orientation. This condition exists on the simulator and on the iPhone device and is easily tested. (Radars for this issue have been closed with updates that the features are working as designed.)
For a workaround, consider recovering the angular orientation directly as just shown. Then, after you determine the device angle, convert from the accelerometer-based angle to a device orientation. Here’s how that might work in code:
// Limited to the four portrait/landscape options - (UIDeviceOrientation) acceleratorBasedOrientation { CGFloat baseAngle = self.orientationAngle; if ((baseAngle > -M_PI_4) && (baseAngle < M_PI_4)) return UIDeviceOrientationPortrait; if ((baseAngle < -M_PI_4) && (baseAngle > -3 * M_PI_4)) return UIDeviceOrientationLandscapeLeft; if ((baseAngle > M_PI_4) && (baseAngle < 3 * M_PI_4)) return UIDeviceOrientationLandscapeRight; return UIDeviceOrientationPortraitUpsideDown; }
Be aware that this example looks only at the x-y plane, which is where most user interface decisions need to be made. This snippet completely ignores the z-axis, meaning that you’ll end up with vaguely random results for the face-up and face-down orientations. Adapt this code to provide that nuance if needed.
The UIViewController class’s interfaceOrientation instance method reports the orientation of a view controller’s interface. Although this is not a substitute for accelerometer readings, many interface layout issues rest on the underlying view orientation rather than device characteristics.
Be aware that, especially on the iPad, a child view controller may use a layout orientation that’s distinct from a device orientation. For example, an embedded controller may present a portrait layout within a landscape split view controller. Even so, consider whether your orientation-detection code is satisfiable by the underlying interface orientation. It may be more reliable than device orientation, especially as the application launches. Develop accordingly.
Calculate a Relative Angle
Screen reorientation support means that an interface’s relationship to a given device angle must be supported in quarters, one for each possible front-facing screen orientation. As the UIViewController automatically rotates its onscreen view, the math needs to catch up to account for those reorientations.
The following method, which is written for use in a UIDevice category, calculates angles so that the angle remains in synchrony with the device orientation. This creates simple offsets from vertical that match the way the GUI is currently presented:
- (float) orientationAngleRelativeToOrientation: (UIDeviceOrientation) someOrientation { float dOrientation = 0.0f; switch (someOrientation) { case UIDeviceOrientationPortraitUpsideDown: {dOrientation = M_PI; break;} case UIDeviceOrientationLandscapeLeft: {dOrientation = -(M_PI/2.0f); break;} case UIDeviceOrientationLandscapeRight: {dOrientation = (M_PI/2.0f); break;} default: break; } float adjustedAngle = fmod(self.orientationAngle - dOrientation, 2.0f * M_PI); if (adjustedAngle > (M_PI + 0.01f)) adjustedAngle = (adjustedAngle - 2.0f * M_PI); return adjustedAngle; }
This method uses a floating-point modulo to retrieve the difference between the actual screen angle and the interface orientation angular offset to return that all-important vertical angular offset.