iOS: Disable Autorotation for a Subview
Autorotation is handled by a view's UIViewController (shouldAutorotateToInterfaceOrientation:
), so one approach is to arrange your hierarchy such that rotatable views are managed by one view controller, and non-rotatable views by another view controller. Both of these UIViewController's root views then need adding to the window/superview.
The subtlety here is that if you have two view controller's views on the same level (i.e. added via addSubview:
), only the first view controller (usually the window's rootViewController
) will receive the shouldAutorotateToInterfaceOrientation:
message.
I used this approach myself to achieve a toolbar that rotates, while the main view does not.
Apple's Technical Q&A QA1688 ("Why won't my UIViewController rotate with the device?") talks a little bit about this issue.
Update for iOS 6:
Autorotation now uses UIViewController
's shouldAutorotate
and supportedInterfaceOrientations
methods. shouldAutorotate
returns YES
by default, but remember that a view controller other than the rootViewController
whose view is a direct subview of the window will NOT receive rotation callbacks anyway.
Sample Code for iOS 6:
Create a new project using the "Single View Application" template, and ensure "Use Storyboards" is checked. We'll use the provided ViewController
class as the rotating view controller (rename it if you like!), and create a second UIViewController
subclass called NonRotatingViewController
. Although this view controller will never even receive the rotation callbacks, for completeness and clarity add the following code in NonRotatingViewController.m
:
- (BOOL)shouldAutorotate
{
return NO;
}
In the MainStoryboard
file, drag out a new view controller object and set its class to NonRotatingViewController
, and set its Storyboard ID to "NonRotatingVC". While you're there, change the rotating view controller view's background color to clear (the non rotating view will be added underneath this one), and add a label to each view. In AppDelegate.m
, add the following code:
#import "NonRotatingViewController.h"
// ...
// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
NonRotatingViewController *nonRotatingVC = [mainStoryboard instantiateViewControllerWithIdentifier:@"NonRotatingVC"];
[self.window addSubview:nonRotatingVC.view];
return YES;
}
This is just instantiating a non rotating view controller and adding its view directly to the window (N.B. at this point the window's rootViewController
has already been set by the storyboard).
Run the project. Rotate the device and marvel at the sight of one label rotating while the other stays still!
Sample Code pre iOS 6:
I did this in a new project - a new View-based Application will do just fine. Add two new view controllers: RotatingViewController
and NonRotatingViewController
. Inside each of their nibs I just added a label to describe whether the view should rotate or not. Add the following code:
'RotatingViewController.m
'
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
'NonRotatingViewController.m
'
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (interfaceOrientation == UIInterfaceOrientationPortrait) { // Or whatever orientation it will be presented in.
return YES;
}
return NO;
}
'AppDelegate.m
'
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RotatingViewController *rotating = [[RotatingViewController alloc] initWithNibName:@"RotatingViewController" bundle:nil];
self.rotatingViewController = rotating;
[rotating release];
NonRotatingViewController *nonRotating = [[NonRotatingViewController alloc] initWithNibName:@"NonRotatingViewController" bundle:nil];
self.nonRotatingViewController = nonRotating;
[nonRotating release];
[self.window addSubview:self.rotatingViewController.view];
[self.window insertSubview:self.nonRotatingViewController.view belowSubview:self.rotatingViewController.view];
[self.window makeKeyAndVisible];
return YES;
}
I hope this helps.
Here is another way. Just place this code into your viewController viewDidLoad:
YourAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
// the view you don't want rotated (there could be a heierarchy of views here):
UIView *nonRotatingView = [[UIView alloc] initWithFrame:CGRectMake(100,0,30,500)];
nonRotatingView.backgroundColor = [UIColor purpleColor];
// make sure self.view and its children are transparent so you can see through to this view that will not be rotated behind self.view.
[delegate.window insertSubview:nonRotatingView belowSubview:self.view];
// you can't put it in the front using:
// [delegate.window insertSubview:nonRotatingView aboveSubview:self.view];
// It will show up the same as before
// you should declare nonRotatingView as a property so you can easily access it for removal, etc.
My approach of solving this problem is doing a countermeasure using UINotification
to detect auto rotation and rotating the view the other way round.
Find the complete code here:
https://gist.github.com/ffraenz/5945301
- (void)orientationDidChangeNotificationReceived:(NSNotification *)notification
{
// find out needed compass rotation
// (as a countermeasure to auto rotation)
float rotation = 0.0;
if (self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
rotation = M_PI;
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
rotation = M_PI / 2.0;
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
rotation = M_PI / (- 2.0);
// rotate compass without auto rotation animation
// iOS rotation animation duration is 0.3
// this excludes the compassView from the auto rotation
// (same effect as in the camera app where controls do rotate and camera viewport don't)
[UIView animateWithDuration:0.3 animations:^(void) {
self.compassView.transform = CGAffineTransformMakeRotation(rotation);
}];
}