Always Constrain a View Controller to Portrait or Landscape Orientationon 2 July 2011

Ensuring View Controllers always displayed in the desired orientationInteresting problem: most of your app allows for any orientation, however one specific view controller is picky and can only be either of the portrait (or landscape) orientations. When switching to this picky view collection, the view is displayed incorrectly and then rotates, though it still is displayed incorrectly.

There is a fix to constrain one view controller to an orientation when switching between controllers in an app and I am sure this is one of many solutions. This was utilized in an iPad app for iOS 4.3.

The Plan

The example code below is meant to be implemented in a view controller (and could be implemented with more work in a UIView). The idea is to rotate the view 90 degrees before it displayed on screen, if it is coming from a landscape orientation and the view can only be portrait specifically. When the view is rotated to portrait, then we want to remove the rotation and assign it back to zero.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
  return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}
 

Despite being constrained to only the two portrait orientations in the code above, the view is still attempted to be displayed in landscape orientation (at least in my experience).

Step One

The first step is setting up the viewWillAppear: method in the view controller. This will be called before the view controller appears and is rendered on screen.

- (void)viewWillAppear:(BOOL)animated {
 
  CGAffineTransform rotateTransform;
  switch ([UIApplication sharedApplication].statusBarOrientation) {
    case UIDeviceOrientationLandscapeLeft:
      rotateTransform = CGAffineTransformMakeRotation(-90*M_PI/180.0);
      break;
    case UIDeviceOrientationLandscapeRight:
      rotateTransform = CGAffineTransformMakeRotation(90*M_PI/180.0);
      break;
     
    default:
      rotateTransform = CGAffineTransformMakeRotation(0);
      break;
  }
 
  rotateTransform = CGAffineTransformTranslate(rotateTransform, +90.0, +90.0);
  [self.view setTransform:rotateTransform];
}
 

The device orientation is determined by [UIApplication sharedApplication].statusBarOrientation. At this time, the device orientation data found at [[UIDevice currentDevice] orientation] I have found to be unreliable at the point in which viewWillAppear: is called. Therefore the statusBarOrientation is utilized. Before the view appears, the orientation is used to determine if the device is in one of the two landscape orientations and the view’s transform property is assigned the rotated transformation object that is created. All of this will rotate the view, even though the view controller thinks it is in the landscape orientation, and display it in the portrait orientation.

Step Two

The second (and last) is to implement the willRotateToInterfaceOrientation:duration: method in your controller. This method is called on a view controller immediately before the interface is about to rotate. The following code will determine if we have already rotated the view, and need to reset it before we rotate to the orientation we are looking for.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
  CGAffineTransform t = self.view.transform;
  if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation) && t.b && t.c) {
    [self.view setTransform:CGAffineTransformMakeRotation(0)];
  }
}
 

The code above is simple. It retrieves the view’s transform information and determines if b and c in the transform matrix are both not equal to or less than 0, otherwise the view has a rotation transformation applied. Then proceeds to reset the views transform rotation to the default of 0.

Small Glitch

There is a small visual glitch that is created because of this process. The view controller and orient itself and respond to the shouldAutorotateToInterfaceOrientation: that is set in the code above. However, the controller will still think it requires a rotation so the black areas on the corners will be displayed as if the the orientation was changing. It is changing, however the view is not, because when it rotates the CGAffineTransformMakeRotation(0) is resetting it, so visually the content stays in the same position. Small, though possibly a deal breaker for some as a worthwhile solution.

I mentioned before there are probably other solutions to this problem, this one seemed to work for me. Let me know if you find any other easier ways to do this in the comments.

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