I searched the web high and low, but couldn't find a definitive answer on this. What's the best way to have only one UIViewController
support landscape mode when it's embedded in a UINavigationController
, which itself is part of a UITabBarController
?
Most solutions, like this one , suggest overriding
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
in the AppDelegate
. This works somewhat, but when returning from the only ViewController
which supports landscape mode, all the other (non-landscape ViewControllers) will be in landscape orientation as well. They don't keep their portrait orientation.
I've seen apps getting this right, so I know that it must be possible somehow. For instance, movie player apps are often portrait-only, but the actual movie player view is presented modally in forced-landscape mode. Upon dismissing the modal viewcontroller, the underlying viewcontroller is still correctly in portrait orientation,
Any hints?
Here's my conclusion after a lot of research:
First, I tried implementing
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window;
in the AppDelegate as described here , which worked for most cases. But apart from feeling quite "hacky", there was one serious gotcha: The workaround for modally displayed view controllers (see section "A small problem with modal controllers") breaks down when, for instance, displaying an AVPlayerViewController
because it implements its own dismiss method and you can't hook into it to set self.isPresented
(unfortunately, viewWillDisappear:
is too late).
So I went with the alternative approach of using subclasses for UITabBarController
and UINavigationController
which feels much cleaner and only slightly more verbose:
#import <UIKit/UIKit.h>
@interface CustomNavigationController : UINavigationController
@end
#import "CustomNavigationController.h"
@implementation CustomNavigationController
- (BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
@end
#import <UIKit/UIKit.h>
@interface CustomTabBarController : UITabBarController
@end
#import "CustomTabBarController.h"
@implementation CustomTabBarController
- (BOOL)shouldAutorotate
{
return [self.selectedViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.selectedViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
@end
After that it's just a matter of adding the following code to your UIViewControllers
:
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
// Presents the `UIViewController` in landscape orientation when it is first displayed
return UIInterfaceOrientationLandscapeLeft;
}
- (NSUInteger)supportedInterfaceOrientations {
// Allows all other orientations (except upside-down)
return UIInterfaceOrientationMaskAllButUpsideDown;
}
Now you may decide the preferred and supported orientations on a per-view-controller-basis. Note that I didn't implement shouldAutorotate
in my view controllers because it defaults to YES, which is what you want if your view controllers should be forced to a certain orientation (yes, they should autorotate to the only supported orientation).
The call chain goes something like this:
CustomTabBarController
, being the window's rootViewController
, is asked for supported/preferred orientations CustomTabBarController
in turn asks its selectedViewController
(view controller of the currently selected tab), which happens to be my CustomNavigationController
CustomNavigationController
asks the embedded topViewController
, which is finally the actual UIViewController
implementing the methods above You still need to allow all device orientations in your build target. And of course, you need to update your storyboards/xibs/classes to use these subclasses instead of the standard UINavigationController
and UITabBarController
classes.
The only downside to this approach is that, at least in my case, I had to add these methods to all of my view controllers to make most view controllers portrait-only and some landscape-capable. But IMHO it's definitely worth it!
You should write the above code to the UINavigationController
subclass. You should define your application orientation and separate your view controller orientation using if statements.
-(BOOL)shouldAutorotate {
if ([[self.viewControllers lastObject] isKindOfClass:[UIViewController class]] ) {
return [[self.viewControllers lastObject] shouldAutorotate];
}
return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
if ([[self.viewControllers lastObject] isKindOfClass:[UIViewController class]]) {
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.