How to Handle Device Rotation for UIViews in iOSon 28 April 2011

Choosing to support orientation changes in a UIViewController is a simple process in iOS. Apple has already provided the methods to attach to. Supporting orientation changes in a UIView is easy, though it is less robust. When listening for orientation changes, the view can respond visually to the rotation of the device. These notifications will also notify when the device is screen-side down or screen-side up.

Illustration of an iPhone rotating in space to show device orientation with a green arrow and a crazy photo of me in the February 2011 Chicago blizzard as the wallpaper

Requesting Notifications for Orientation Changes

Code to support orientation changes can be added at any time throughout the life of a view. The code to start listening for notifications of orientation changes is:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object: nil];
 

UIDevice according to the Apple documentation is “a singleton instance representing the current device.” Calling the class method1 beginGeneratingDeviceOrientationNotifications tells the device to fire up the accelerometer to start sending out orientation change notifications. However that one line alone will not respond to the notifications. The second line will and adds the view (self) as an observer to the main notification center. It will then start receiving notifications of type UIDeviceOrientationDidChangeNotification. Each time the orientation changes the method, via @selector, will call our custom method deviceOrientationDidChange:. The colon representing a parameter will be a variable of type NSNotification.

Responding to Device Rotation

Using the code above, the view is ready to respond with the following method.

UIDeviceOrientation currentOrientation;
 
- (void)deviceOrientationDidChange:(NSNotification *)notification {
  //Obtaining the current device orientation
  UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
 
  //Ignoring specific orientations
  if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown || orientation == UIDeviceOrientationUnknown || currentOrientation == orientation) {
    return;
  }
  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(relayoutLayers) object:nil];
  //Responding only to changes in landscape or portrait
  currentOrientation = orientation;
  //
  [self performSelector:@selector(orientationChangedMethod) withObject:nil afterDelay:0];
}
 

The first line sets up currentOrientation to hold the last orientation. A lot of data comes through and the slightest movement can trigger another notification. So to prevent unnecessary reaction, the last orientation is stored.

The method is the one mentioned above and takes the parameter of the type NSNotification. Then the current device orientation is stored in a local variable.

Using orientation, specific orientations are ignored. I only want the app to respond to rotation changes from portrait to landscape. So we check to make sure the orientation is not changing to face up or face down. The device can have issues determining the orientation. This is why it will also ignore the unknown (UIDeviceOrientationUnknown) notification. Finally, if the last orientation was the same (to avoid unnecessary rotation code execution); ignored.

Now that the orientation is not face up, face down, unknown, or the same as the last event, the currentOrientation variable is set. The last line of the method performs a @selector. This will call another method with optional parameters after a specified amount of time. This is necessary because any changes based on the bounds of the view (self) will not be updated by the time this method has been called.

Extra Orientation Notes

After implementing the code above in my own view, there were times when the device rotated 180 degrees so the orientation change was handled by the parent UIViewController. Therefore the updates I was doing in my orientationChangedMethod were unnecessary. The following code was added to check for similar orientations and return if no change was needed.

if ((UIDeviceOrientationIsPortrait(currentOrientation) && UIDeviceOrientationIsPortrait(orientation)) ||
  (UIDeviceOrientationIsLandscape(currentOrientation) && UIDeviceOrientationIsLandscape(orientation))) {
  //still saving the current orientation
  currentOrientation = orientation;
  return;
}
 

If the view implementing this code requires actions based on an orientation, it is recommended to base it on the default orientation UIDeviceOrientationPortrait or UIDeviceOrientationLandscape, based on the need for your app. Otherwise the default value for [[UIDevice currentDevice] orientation] is UIDeviceOrientationUnknown and can lead to unexpected results if code depends on this orientation being correct.


  1. Class methods are designated in the Apple documentation with the “+” in front of the method name. 

If you enjoyed this, use this shorter link to share: http://the.ichibod.com/s/twopir