Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape?
Solution 1:
I found a much better way that works independent of the nav controller. Right now, I have this working when embedded in a nav controller, and when NOT embedded (although I'm not using the nav controller right now, so there may be some bug I've not seen - e.g. the PUSH transition animation might go funny, or something)
Two NIBs, using Apple's naming convention. I suspect that in iOS 6 or 7, Apple might add this as a "feature". I'm using it in my apps and it works perfectly:
- triggers on WILL rotate, not SHOULD rotate (waits until the rotate anim is about to start)
- uses the Apple naming convention for landscape/portrait files (Default.png is Default-landscape.png if you want Apple to auto-load a landscape version)
- reloads the new NIB
- which resets the
self.view
- this will AUTOMATICALLY update the display - and then it calls
viewDidLoad
(Apple will NOT call this for you, if you manually reload a NIB)
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) )
{
[[NSBundle mainBundle] loadNibNamed: [NSString stringWithFormat:@"%@-landscape", NSStringFromClass([self class])]
owner: self
options: nil];
[self viewDidLoad];
}
else
{
[[NSBundle mainBundle] loadNibNamed: [NSString stringWithFormat:@"%@", NSStringFromClass([self class])]
owner: self
options: nil];
[self viewDidLoad];
}
}
Solution 2:
You have to load one view, then check orientation and load another if needed. You check orientation in shouldAutorotateToInterfaceOrientation:
returning yes if you want to rotate.
I use a navigation controller to manage the transition. If I have the portrait view up and the device rotates, I push the landscape view and then pop the landscape view when it return to portrait.
Edit:
I return YES for all orientations in shouldAutorotateToInterfaceOrientation: but will this be called when the app launches? Do you push your view inside of this function?
The orientation constants are not globals you query but rather part of the messages sent the controller by the system. As such, you cannot easily detect orientation before a view controller loads. Instead, you hardwire the app to start in a particular orientation (usually portrait) and then immediately rotate. (See mobile Safari. It always starts in portrait and then rotates to landscape.)
These are the two methods I used to swap out my portrait and landscape views.
All three view controllers have this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
The portrait has this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
[self.nav pushViewController:rightLVC animated:NO];
}
if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
[self.nav pushViewController:leftLVC animated:NO];
}
}
Each landscape controller has this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
[self.nav popViewControllerAnimated:NO];
}
The app starts in portrait. If the orientation of the device is landscape, it pushes the appropriate landscapes. When the device rotates back to portrait, it pops the landscape. To the user it looks like the same view reorganizing itself for a different orientation.
Solution 3:
Really like Adam's answer - I modified it slightly to allow for initial loading of nib in either portrait or landscape
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if( !nibNameOrNil ) nibNameOrNil = [self nibNameRotated:[[UIApplication sharedApplication] statusBarOrientation]];
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (NSString*) nibNameRotated:(UIInterfaceOrientation)orientation
{
if( UIInterfaceOrientationIsLandscape(orientation)) return [NSString stringWithFormat:@"%@-landscape", NSStringFromClass([self class])];
return [NSString stringWithFormat:@"%@", NSStringFromClass([self class])];
}
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
NSString *nibname = [self nibNameRotated:toInterfaceOrientation];
[[NSBundle mainBundle] loadNibNamed:nibname owner:self options:nil];
[self viewDidLoad];
}
Solution 4:
I like TechZen's accepted answer but as pointed out in Adam's comment it leaves the portrait view controller in the navigation stack when you rotate to landscape. This means tapping the back button in the navigation bar while in landscape will take the user to the portrait view. A coworker and I are trying the approach of manually removing the extra item from the nav stack. It seems to work but I have very little experience with it yet, so I make no promises. Here is sample code:
To go from portrait to landscape:
[[self navigationController] pushViewController:landscapeViewController animated:NO];
[self removeFromNavigationStack:[MyPortraitViewController class]];
To go back to portrait:
[[self navigationController] pushViewController:portraitViewController animated:NO];
[self removeFromNavigationStack:[MyLandscapeViewController class]];
If you don't use animated:NO you may get warnings regarding the state of the navigation.
Helper:
- (void)removeFromNavigationStack:(Class)oldClass {
UINavigationController *nav = [self navigationController];
NSMutableArray *newStack = [NSMutableArray array];
for (UIViewController *vc in nav.viewControllers) {
if (![vc isMemberOfClass:[oldClass class]]) {
[newStack addObject: vc];
}
[nav setViewControllers:newStack animated:NO];
}
Solution 5:
One more choice, TPMultiLayoutViewController.