简体   繁体   中英

Restrict/Force only some view controllers to specific orientations when using UITabBarController and/or UINavigationController

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:

CustomNavigationController

CustomNavigationController.h

#import <UIKit/UIKit.h>

@interface CustomNavigationController : UINavigationController

@end

CustomNavigationController.m

#import "CustomNavigationController.h"

@implementation CustomNavigationController

- (BOOL)shouldAutorotate
{
    return [self.topViewController shouldAutorotate];
}

- (NSUInteger)supportedInterfaceOrientations
{
    return [self.topViewController supportedInterfaceOrientations];
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return [self.topViewController preferredInterfaceOrientationForPresentation];
}

@end

CustomTabBarController

CustomTabBarController.h

#import <UIKit/UIKit.h>

@interface CustomTabBarController : UITabBarController

@end

CustomTabBarController.m

#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:

  • The CustomTabBarController , being the window's rootViewController , is asked for supported/preferred orientations
  • The CustomTabBarController in turn asks its selectedViewController (view controller of the currently selected tab), which happens to be my CustomNavigationController
  • The 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM