IOS6: 如何将一些视图限制为纵向,并允许其他视图旋转?

我有一个 iPhone 应用程序,它使用一个 UINavigationController来呈现一个向下钻取的界面: 首先是一个视图,然后是另一个,深达四层。我希望前三个视图仅限于肖像方向,只有最后一个视图应允许旋转到景观。当从第四视图返回到第三视图和第四视图是在横向方向,我希望一切旋转回肖像。

在 iOS5中,我简单地在每个视图控制器中定义了 shouldAutorotateToInterfaceOrientation:,以返回允许的方向的 YES。一切工作如上所述,包括返回到肖像,即使设备正在举行横向时,从视图控制器 # 4返回 # 3。

在 iOS6中,所有的视图控制器都可以旋转到横向,打破那些不应该旋转的视图控制器。IOS6的发布说明中写道

更多的责任转移到应用程序和应用程序委托。现在,iOS 容器(如 UINavigationController)不会咨询它们的子容器来决定是否应该自动旋转。[ ... ]当设备旋转或视图控制器呈现全屏模式表示风格时,系统要求最顶端的全屏视图控制器(通常是根视图控制器)提供其支持的界面方向。而且,只有当这个视图控制器从它的 shouldAutorotate方法返回 YES 时,才能检索到支持的方向。[ ... ]系统通过将应用程序的 supportedInterfaceOrientationsForWindow:方法返回的值与最顶端全屏控制器的 supportedInterfaceOrientations方法返回的值相交来确定是否支持方向。

所以我子类化了 UINavigationController,给了我的 MainNavigationController一个布尔属性 landscapeOK,并使用它返回 supportedInterfaceOrientations中允许的方向。然后在每个视图控制器的 viewWillAppear:方法中都有一行这样的代码

    [(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

告诉我的 MainNavigationController想要的行为。

问题来了: 如果我现在导航到我的第四个视图在肖像模式,把手机在它旋转到横向。现在我按后退按钮返回到我的第三个视图,这应该是工作肖像只。但它不会回转。我怎么才能做到呢?

我尽力了

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

在我的第三个视图控制器的 viewWillAppear方法中,但是它什么也不做。这是一个错误的调用方法,还是一个错误的调用位置,或者我应该用一种完全不同的方式来实现整个过程?

56387 次浏览

I'd like to give a partial answer to my own question. I found the following line of code, used in the viewWillAppear method of my third UIViewController, to work:

[[UIDevice currentDevice]
performSelector:NSSelectorFromString(@"setOrientation:")
withObject:(id)UIInterfaceOrientationPortrait];

But I don't really like this solution. It is using a trick to assign to a read-only property which according to Apple documentation represents the physical orientation of the device. It's like telling the iPhone to jump to the correct orientation in the hand of the user.

I am very tempted to leave this in my app since it simply works. But it doesn't feel right so I'd like to leave the question open for a clean solution.

Please use the following method to solve this issue

- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}

return only the orientation you want!!!

I had the same problem and found a solution that works for me. To make it work, it is not sufficient to implement - (NSUInteger)supportedInterfaceOrientations in your UINavigationController. You also need to implement this method in your controller #3, which is the first one to be portrait-only after popping controller #4. So, I have the following code in my UINavigationController:

- (BOOL)shouldAutorotate
{
return YES;
}


- (NSUInteger)supportedInterfaceOrientations
{
if (self.isLandscapeOK) {
// for iPhone, you could also return UIInterfaceOrientationMaskAllButUpsideDown
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}

In view controller #3, add the following:

- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}

You don't need to add anything to your view controllers #1, #2, and #4. This works for me, I hope it will help you.

Add a CustomNavigationController

Override these methods in it:

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


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


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

Now add all orientations in the plist

enter image description here

In the view controller add only the required ones:

-(BOOL)shouldAutorotate
{
return YES;
}


-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}

these methods override the navigation controller methods

This is I am using for orientation support in ios 6.0

-(BOOL)shouldAutorotate{
return YES;
}


- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll;
}




- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}

After looking through every answer in countless similar questions on SO, none of the answers worked for me, but they did give me some ideas. Here's how I ended up solving the problem:

First, make sure your Supported Interface Orientations in your project's target contain all orientations that you want for your rotating view.

enter image description here

Next, make a category of UINavigationController (since Apple says not to subclass it):

@implementation UINavigationController (iOS6AutorotationFix)


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


@end

Import that category and the view controller that you want to be able to rotate (which I'll call RotatingViewController) to your highest level view controller, which should contain your navigation controller. In that view controller, implement shouldAutorotate as follows. Note that this should not be the same view controller that you want to rotate.

-(BOOL)shouldAutorotate {


BOOL shouldRotate = NO;


if ([navigationController.topViewController isMemberOfClass:[RotatingViewController class]] ) {
shouldRotate = [navigationController.topViewController shouldAutorotate];
}


return shouldRotate;
}

Finally, in your RotatingViewController, implement shouldAutorotate and supportedInterfaceOrientations as follows:

-(BOOL)shouldAutorotate {
// Preparations to rotate view go here
return YES;
}


-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
}

The reason you need to do this is because iOS 6 gives control of rotation to the root view controller instead of the top view controlller. If you want an individual view's rotation to behave differently than other views in the stack, you need to write a specific case for it in the root view controller.

You want to Force iOS 6 app portrait only then you can add to a UIViewController subclass below methods

- (BOOL)shouldAutorotate {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return YES;
} else {
return NO;
}
}




- (NSUInteger)supportedInterfaceOrientations {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}

This might not work for everyone, but it works great for me. Instead of implementing...

[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];

in viewWillAppear in all of my controllers, I decided to centralize this process inside of my UINavigationController subclass by overriding the UINavigationControllerDelegate method navigationController:willShowViewController:animated:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {


self.previousVCLandscapeOK = self.isLandscapeOK; // Store the current VC's orientation preference before pushing on the new VC so we can set this again from within the custom "back" method
self.isLandscapeOK = NO; // Set NO as default for all VC's
if ([viewController isKindOfClass:[YourViewController class]]) {
self.isLandscapeOK = YES;
}
}

I have found that this delegate method doesn't get called when popping a VC off of the nav stack. This wasn't an issue for me because I am handling the back functionality from within my UINavigationController subclass so that I can set the proper navigation bar buttons and actions for specific VC's like this...

if ([viewController isKindOfClass:[ShareViewController class]]) {


UIButton* backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 57, 30)];
[backButton setImage:[UIImage imageNamed:@"back-arrow"] forState:UIControlStateNormal];
[backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backButtonItem;


UIImageView* shareTitle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share-title"]];
[shareTitle setContentMode:UIViewContentModeScaleAspectFit];
[shareTitle setFrame:CGRectMake(0, 0, shareTitle.frame.size.width - 10, shareTitle.frame.size.height - 10)];
viewController.navigationItem.titleView = shareTitle;


} else if(...) {
...
}

Here is what my back method looks like to handle popping the VC off of the stack and to set the appropriate rotation preference...

- (void)back {
self.isLandscapeOK = self.previousVCLandscapeOK;
self.previousVCLandscapeOK = NO;
[self popViewControllerAnimated:YES];
}

So, as you can see, basically all that is happening is I'm first setting me two properties...

@property (nonatomic) BOOL isLandscapeOK;
@property (nonatomic) BOOL previousVCLandscapeOK;

in navigationController:willShowViewController:animated: which will determine what the supported orientations are within that VC that is about to be presented. When popping a VC, my custom "back" method is being called and I'm then setting the isLandscapeOK to what was stored via the previousVCLandscapeOK value.

As I said, this might not work for everyone, but it works great for me and I don't have to worry about adding code to each of my view controllers, I was able to keep it all centralized in the UINavigationController subclass.

Hope this helps someone as it did me. Thanks, Jeremy.

I don't have enough reputation to comment on @Brian's answer so I'll add my note here.

Brian mentioned that iOS6 gives the rotation control to the rootViewController - this could not only be a UINavigationController as mentioned but also a UITabBarController, which it was for me. My structure looks like this:

  • UITabBarController
    • UINavigationController
      • UIViewControllers ...
    • UINavigationController
      • UIViewControllers ...

So I added the methods first in a custom UITabBarController, then in a custom UINavigationController and then lastly in the specific UIViewController.

Example from the UITabBarController and UINavigationController:

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


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


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return [self.viewControllers.lastObject shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}


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

Go to you Info.plist file and make the changeenter image description here

I wanted to have all my VCs locked to portrait orientation except one. This is what worked for me.

  1. Add support for all orientations in the plist file.
  2. In the root view controller, detect the kind of view controller thats on top of the window and set the orientation of the app accordingly in the supportedInterfaceOrientations method. For example, I needed my app to rotate only when the webview was on top of the stack. Here's what I added in my rootVC :

    -(NSUInteger)supportedInterfaceOrientations
    {
    UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
    if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
    return UIInterfaceOrientationMaskAllButUpsideDown;
    }
    return UIInterfaceOrientationMaskPortrait;
    }
    

I don't have enough reputation to answer @Ram S question under @micmdk reply so I'll add my note here.

When you use UITabbarController, try to change self.viewControllers.lastObject in @micmdk's code to self.selectedViewController like this:

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


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


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}


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

Being that this is a highly viewed thread. I thought I would add what I believe is the easiest answer. This works for ios8 and up

-(BOOL)shouldAutorotate
{
return YES;
}

and

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}

That's it. Enjoy!

Oh, and my ViewControllers are embedded in a navigation controller, which I did not need to subclass or configure in any way.

I've solved the same kind of issue.

If you are using the UINavigationController to push the view controllers, you have to set the methods below.

extension UINavigationController{


override open var shouldAutorotate: Bool {


if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return true
}
return false
}


override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {


if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return .portrait
}
return .landscapeRight


}
override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {


if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return .portrait
}
return .landscapeRight
}
}

In place of LoginViewController use which UIViewController you want show. In my case, I want to show the LoginViewController in the Portrait mode other ViewControllers in landscape mode.

You can implement and override shouldAutorotate() and supportedInterfaceOrientations var in all of your View Controller classes that should be presented in a different orientations than ones defined in PLIST of your app.

However, in a nontrivial User Interface you might face a problem to add it in dozens of classes and you do not want to make all of them subclass of several common that supports it (MyBaseTableViewController, MyBaseNavigationController and MyBaseTabBarController).

Since you cannot override those method/var on UIViewController directly, you may do that on its subclasses that are typically base classes of yours like UITableViewController, UINavigationController and UITabBarController.

So you may implement a few extensions and still setup MyPreciousViewController to show in a different orientations than all others like this Swift 4 code snippet:

extension UITableViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {


if let last = self.navigationController?.childViewControllers.last,
last != self {
return last.supportedInterfaceOrientations
} else {
return [.portrait]
}
}
}


extension MyPreciousViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {


return [.portrait,.landscape]
}
}




extension UINavigationController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {


return [.portrait]
}
}




extension UITabBarController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {


return [.portrait]
}
}