Apple provide an easy way of determining the device orientation, a
call to UIDevice
will return the
current orientation of the device:
UIDevice *device = [UIDevice currentDevice]; UIDeviceOrientation orientation = device.orientation;
This call will return a UIDeviceOrientation
that can be: UIDeviceOrientationUnknown
, UIDeviceOrientationPortrait
, UIDeviceOrientationPortraitUpsideDown
, UIDeviceOrientationLandscapeLeft
, UIDeviceOrientationLandscapeRight
or UIDeviceOrientationFaceUp
. The sensor underlying
this call is the accelerometer, and you’ll see later in this chapter how
to retrieve the device orientation directly from the raw accelerometer
readings.
Warning
As of the time of writing under iOS 4.3 the device does not
correctly report a proper orientation when your application is first
launched, with UIDevice
returning
null
when queried.
Lets go ahead and modify the Accelerometer application to display the device orientation. Click on the AccelerometerViewController.h interface file to open it in the Standard Editor and add the following code, highlighted below, to the class interface:
@interface AccelerometerViewController : UIViewController <UIAccelerometerDelegate> { IBOutlet UILabel *xLabel; IBOutlet UILabel *yLabel; IBOutlet UILabel *zLabel; IBOutlet UIProgressView *xBar; IBOutlet UIProgressView *yBar; IBOutlet UIProgressView *zBar; IBOutlet UILabel *orientationLabel; UIAccelerometer *accelerometer; } - (NSString *)stringFromOrientation:(UIDeviceOrientation) orientation; @end
We’re going to display the current orientation using in a UILabel
, so we’re going to have to write a
convenience method stringFromOrienation:
to convert the UIDeviceOrientation
type returned by the
UIDevice
to an NSString
to display in that label.
Make sure you’ve saved your changes, and click on the corresponding AccelerometerViewController.m implementation file and add the following method:
- (NSString *)stringFromOrientation:(UIDeviceOrientation) orientation { NSString *orientationString; switch (orientation) { case UIDeviceOrientationPortrait: orientationString = @"Portrait"; break; case UIDeviceOrientationPortraitUpsideDown: orientationString = @"Portrait Upside Down"; break; case UIDeviceOrientationLandscapeLeft: orientationString = @"Landscape Left"; break; case UIDeviceOrientationLandscapeRight: orientationString = @"Landscape Right"; break; case UIDeviceOrientationFaceUp: orientationString = @"Face Up"; break; case UIDeviceOrientationFaceDown: orientationString = @"Face Down"; break; case UIDeviceOrientationUnknown: orientationString = @"Unknown"; break; default: orientationString = @"Not Known"; break; } return orientationString; }
Once you have added the stringFromOrienation:
method, add the following
code, highlighted below, to the existing accelerometer:didAccelerate:
method in the same
class:
- (void)accelerometer:(UIAccelerometer *)meter didAccelerate:(UIAcceleration *)acceleration { xLabel.text = [NSString stringWithFormat:@"%f", acceleration.x]; xBar.progress = ABS(acceleration.x); yLabel.text = [NSString stringWithFormat:@"%f", acceleration.y]; yBar.progress = ABS(acceleration.y); zLabel.text = [NSString stringWithFormat:@"%f", acceleration.z]; zBar.progress = ABS(acceleration.z); UIDevice *device = [UIDevice currentDevice]; orientationLabel.text = [self stringFromOrientation:device.orientation]; }
Make sure you’ve saved your changes, and click on the AccelerometerViewController.xib file to open it
in Interface Builder. Drag and drop a UILabel
from the Object Library into the View.
Go ahead and resize and center up the text using the Attributes inspector
from the Utilities panel.
Close the Utilities panel and open the Assistant Editor, which
should show the corresponding interface file for the view controller.
Control-click and drag and connect the UILabel
element to the orientationLabel
outlet in your code, as in
Figure 4-5.
Save your changes, and click Run button in the Xcode toolbar to compile and deploy your application to your device. If all goes well you should see something much like Figure 4-6. As you move the device around, the label will update itself to reflect the current device orientation.
Instead of querying UIDevice
you can use the raw accelerometer readings to determine the device
orientation directly using the atan2
function as shown below:
float x = -[acceleration x]; float y = [acceleration y]; float angle = atan2(y, x);
Note
For any real arguments x and y that are not both equal to zero, atan2(y, x) is the angle in radians between the positive x-axis of a plane and the point given by the specified coordinates on it. The angle is positive for counter-clockwise angles, and negative for clockwise angles.
Let’s go ahead and modify the accelerometer:didAccelerate:
method to
calculate the orientation. Click on the AccelerometerViewController.m implementation
file to open it in the Standard Editor and replace these lines:
UIDevice *device = [UIDevice currentDevice]; orientationLabel.text = [self stringFromOrientation:device.orientation];
with the code highlighted below:
- (void)accelerometer:(UIAccelerometer *)meter didAccelerate:(UIAcceleration *)acceleration { xLabel.text = [NSString stringWithFormat:@"%f", acceleration.x]; xBar.progress = ABS(acceleration.x); yLabel.text = [NSString stringWithFormat:@"%f", acceleration.y]; yBar.progress = ABS(acceleration.y); zLabel.text = [NSString stringWithFormat:@"%f", acceleration.z]; zBar.progress = ABS(acceleration.z); float x = -[acceleration x]; float y = [acceleration y]; float angle = atan2(y, x); if(angle >= −2.25 && angle <= −0.75) { orientationLabel.text = [self stringFromOrientation:UIInterfaceOrientationPortrait]; } else if(angle >= −0.75 && angle <= 0.75){ orientationLabel.text = [self stringFromOrientation:UIInterfaceOrientationLandscapeRight]; } else if(angle >= 0.75 && angle <= 2.25) { orientationLabel.text = [self stringFromOrientation:UIInterfaceOrientationPortraitUpsideDown]; } else if(angle <= −2.25 || angle >= 2.25) { orientationLabel.text = [self stringFromOrientation:UIInterfaceOrientationLandscapeLeft]; } }
If you save your changes, and click on the Run button to rebuild and deploy your application onto your device, there should see little or no change in the application’s operation. However, having access to each component of the orientation opens up many opportunities for creating tilt-based controls.
In addition to directly querying the UIDevice
object for the current orientation, a
program can request to be notified of changes in the device’s
orientation by registering itself as an observer.
We can once again modify the Accelerometer application to make use
of this feature. Open the AccelerometerViewController.m file in the
Standard Editor and delete the code added in the previous section from
the accelerometer:didAccelerate:
method.
If you quickly rebuild the application at this point and deploy it
to your device you will see that the UILabel
now reads “Label” and will no longer
be updated as the device orientation changes.
Once you’ve confirmed that, add the following method:
-(void) viewWillAppear:(BOOL) animated{ [super viewWillAppear:animated]; [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedRotation:) name:UIDeviceOrientationDidChangeNotification object:nil]; }
Here we ask the UIDevice
class
to start generating orientation notifications, and we register the view
controller as an observer. Next add the selector method, shown
below:
-(void) receivedRotatation:(NSNotification*) notification { UIDevice *device = [UIDevice currentDevice]; orientationLabel.text = [self stringFromOrientation:device.orientation]; }
Here we simply update the UILabel
with the device orientation every time
a UIDeviceOrientationDidChangeNotification
event
is received.
Finally we need to remember to remove the program as an observer to stop the generation of messages during the tear down of our view controller. Add the following method to your code:
-(void) viewWillDisappear:(BOOL) animated{ [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver: self]; [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; }
If you once again save your changes and click on the Run button to rebuild and deploy the application to your device, you will again see little or no change in the application’s operation.
A useful thing to know a lot of the time is the answer to the question “which way is up?” You can use the same method used earlier to determine the device orientation and graphically show this in the View.
First you’re going to need an image of an arrow. Download, or draw in the graphics package of your choice, an image of an arrow pointing to the left on a transparent background. Save it as, or convert it to, a PNG format file. Drag-and-drop this into your Xcode Project remembering to tick the “Copy items into destination group’s folder (if needed)” check box in the pop up dialog that appears when you drop the files into Xcode (see Figure 4-7).
Click on the AccelerometerViewController.xib file to open
it, and drag-and-drop a UIImageView
from the Object Library onto your View. Position it below the three
UIProgressBar
elements, and resize
the bounding box to be a square using the Size inspector of the Utility
Pane. In the Attributes inspector of the Utility Pane, change the Image
property to be the arrow image that you added to your project. Set the
View mode to be “Aspect Fit”. Uncheck the “Opaque” box in the Drawing
section so that the arrow is rendered correctly with a transparent
background. Finally, use the “Image” drop-down to select the arrow.png
image to be displayed in the UIImageView
(see Figure 4-8).
Close the Utility Pane and open the Assistant Editor.
Control-click and drag from the UIImageView
in your View to the arrowImage
outlet in the Assistant Editor, as
in Figure 4-9, and add an arrrowImage
outlet.
After doing so your interface file should look as below:
@interface AccelerometerViewController :
UIViewController <UIAccelerometerDelegate> {
IBOutlet UILabel *xLabel;
IBOutlet UILabel *yLabel;
IBOutlet UILabel *zLabel;
IBOutlet UIProgressView *xBar;
IBOutlet UIProgressView *yBar;
IBOutlet UIProgressView *zBar;
IBOutlet UIImageView *arrowImage;
IBOutlet UILabel *orientationLabel;
UIAccelerometer *accelerometer;
}
Close the Assistant Editor and switch to the Standard Editor. Go
ahead and click on the AccelerometerViewController.m implementation
file. Add the code highlighted below to the accelerometer:didAccelerate:
method:
- (void)accelerometer:(UIAccelerometer *)meter didAccelerate:(UIAcceleration *)acceleration { xLabel.text = [NSString stringWithFormat:@"%f", acceleration.x]; xBar.progress = ABS(acceleration.x); yLabel.text = [NSString stringWithFormat:@"%f", acceleration.y]; yBar.progress = ABS(acceleration.y); zLabel.text = [NSString stringWithFormat:@"%f", acceleration.z]; zBar.progress = ABS(acceleration.z); float x = -[acceleration x]; float y = [acceleration y]; float angle = atan2(y, x); [arrowImage setTransform:CGAffineTransformMakeRotation(angle)]; }
That’s it. Save your changes again and click on the Run button to compile and deploy the application to your device. Keep the device face towards you and rotate it in flat plane, you should see that the arrow moves as you do so, keeping its orientation pointing upwards (see Figure 4-9).
Apple provides convenience methods to determine whether the current device orientation is portrait:
UIDevice *device = [UIDevice currentDevice]; UIDeviceOrientation orientation = device.orientation; BOOL portrait = UIDeviceOrientationIsPortrait( orientation );
or landscape:
BOOL landscape = UIDeviceOrientationIsLandscape( orientation );
These methods return YES
if the
device is in portrait or landscape mode respectively; otherwise they
return NO
.
Get Basic Sensors in iOS now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.