【问题标题】:iOS 6: How do I restrict some views to portrait and allow others to rotate?iOS 6:如何将某些视图限制为纵向并允许其他视图旋转?
【发布时间】:2012-09-19 18:27:19
【问题描述】:

我有一个 iPhone 应用程序,它使用 UINavigationController 来呈现向下钻取界面:首先是一个视图,然后是另一个视图,最多四层深度。我希望将前三个视图限制为纵向,并且只允许最后一个视图旋转到横向。当从第四个视图返回到第三个视图并且第四个视图处于横向时,我希望所有内容都旋转回纵向。

在 iOS 5 中,我只是在我的每个视图控制器中定义了 shouldAutorotateToInterfaceOrientation:,以便为允许的方向返回 YES。一切都如上所述,包括从视图控制器 #4 返回到 #3 时,即使设备处于横向状态,也可以返回纵向。

在 iOS 6 中,所有视图控制器都旋转为横向,从而破坏了原本不应该这样做的视图控制器。 iOS 6 发行说明说

更多的责任正在转移到应用和应用代理身上。现在,iOS 容器(例如UINavigationController)不会咨询他们的孩子来确定他们是否应该自动旋转。 [...] 每当设备旋转或视图控制器以全屏模式呈现样式呈现时,系统都会向最顶层的全屏视图控制器(通常是根视图控制器)询问其支持的界面方向。此外,仅当此视图控制器从其shouldAutorotate 方法返回 YES 时,才会检索支持的方向。 [...] 系统通过将应用程序的 supportedInterfaceOrientationsForWindow: 方法返回的值与最顶部全屏控制器的 supportedInterfaceOrientations 方法返回的值相交来确定是否支持方向。

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

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

告诉我的MainNavigationController 所需的行为。

问题来了:如果我现在以纵向模式导航到我的第四个视图并将手机翻过来,它会旋转到横向。现在我按下后退按钮返回到我的第三个视图,它应该只用于纵向。但它不会旋转回来。我如何让它做到这一点?

我试过了

    [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]

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

【问题讨论】:

    标签: ios iphone ios6 autorotate


    【解决方案1】:

    我遇到了同样的问题,并找到了适合我的解决方案。 为了使其工作,在 UINavigationController 中实现 - (NSUInteger)supportedInterfaceOrientations不够的。 您还需要在控制器 #3 中实现此方法,这是弹出控制器 #4 后第一个仅显示纵向的方法。 所以,我的 UINavigationController 中有以下代码:

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

    在视图控制器 #3 中,添加以下内容:

    - (NSUInteger)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskPortrait;
    }
    

    您无需向视图控制器 #1、#2 和 #4 添加任何内容。 这对我有用,希望对你有帮助。

    【讨论】:

    • 您解决了人们数月来试图解决的问题!你应该为此得到很多赞誉!我在 UIViewController 上的一个类别中添加了supportedInterfaceOrientations,它可以完美地作为那些仅纵向控制器的默认设置。
    • 在关于 SO 的 100 个不同答案中,这是一个真正有效的答案。谢谢:-)
    • 我已经设置好了,但从纵向返回到仅限横向的视图控制器时,方向仍然错误。它不会自动旋转到风景 - 还有其他人看到它吗?
    • 如果所需的横向视图控制器是从一个 segue 中呈现的并且没有导航控制器怎么办?
    • 编辑最后一个为 NSUInteger 作为返回类型。
    【解决方案2】:

    添加一个CustomNavigationController

    覆盖其中的这些方法:

    -(BOOL)shouldAutorotate
    {
        return [[self.viewControllers lastObject] shouldAutorotate];
    }
    
    -(NSUInteger)supportedInterfaceOrientations
    {
        return [[self.viewControllers lastObject] supportedInterfaceOrientations];
    }
    
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
    {
        return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
    }
    

    现在在 plist 中添加所有方向

    在视图控制器中只添加需要的:

    -(BOOL)shouldAutorotate
    {
        return YES;
    }
    
    -(NSUInteger)supportedInterfaceOrientations
    {
        return UIInterfaceOrientationMaskPortrait;
    }
    

    这些方法会覆盖导航控制器方法

    【讨论】:

    • 你真的用这个答案搞定了!太感谢了。你真的帮了很多忙。一段时间以来,这对我来说一直是个大问题。干得好。
    • 谢谢!这花了我很多时间和精力来弄清楚!
    • 你是救生员....这也适用于我的应用程序。实际上我在我的应用程序中想要的是我所有的ViewController 都处于纵向模式,而我的一个ViewController 处于横向和纵向模式。我还在 iOS 5.0 和 iOS 6.0 中提供支持,这就是为什么我很难解决问题,但这是一个很好的解决方案。我在我的根视图控制器中添加 CustomNavigationController 并根据我的需要提供支持。再次感谢。
    • 太棒了。正是我需要的。谢谢。
    • 如果所需的横向视图控制器是从一个 segue 中呈现的并且没有导航控制器怎么办?
    【解决方案3】:

    在查看了关于 SO 的无数类似问题中的每个答案后,没有一个答案对我有用,但它们确实给了我一些想法。以下是我最终解决问题的方法:

    首先,确保项目目标中支持的界面方向包含旋转视图所需的所有方向。

    接下来,创建一个UINavigationController 的类别(因为Apple 说不要对其进行子类化):

    @implementation UINavigationController (iOS6AutorotationFix)
    
    -(BOOL)shouldAutorotate {
        return [self.topViewController shouldAutorotate];
    }
    
    @end
    

    将该类别和您希望能够旋转的视图控制器(我将其称为RotatingViewController)导入最高级别的视图控制器,该控制器应包含您的导航控制器。在该视图控制器中,按如下方式实现shouldAutorotate。请注意,这不应与您要旋转的视图控制器相同。

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

    最后,在您的RotatingViewController 中,实现shouldAutorotatesupportedInterfaceOrientations,如下所示:

    -(BOOL)shouldAutorotate {
        // Preparations to rotate view go here
        return YES;
    }
    
    -(NSUInteger)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
    }
    

    您需要这样做的原因是因为 iOS 6 将旋转控制权交给了根视图控制器,而不是顶视图控制器。如果您希望单个视图的旋转行为与堆栈中的其他视图不同,则需要在根视图控制器中为其编写特定案例。

    【讨论】:

    • 这是一个值得更多爱的优秀答案。这是它包含的信息,我在其他任何地方都找不到,这很关键:如果您希望单个视图的旋转行为与堆栈中的其他视图不同,您需要为其编写一个特定的案例在根视图控制器中
    • 我想如果你有一个 navigationController 作为你的应用故事板的第一个成员作为初始控制器,这将起作用,但是当初始控制器只是一个 ViewController 的情况下呢?
    【解决方案4】:

    我没有足够的声誉来评论 @Brian 的回答,所以我会在这里添加我的注释。

    Brian 提到 iOS6 为 rootViewController 提供了旋转控制 - 这不仅可以是前面提到的 UINavigationController,还可以是 UITabBarController,它是为我设计的。我的结构如下所示:

    • UITabBar 控制器
      • UINavigationController
        • UIViewControllers ...
      • UINavigationController
        • UIViewControllers ...

    所以我首先在自定义 UITabBarController 中添加方法,然后在自定义 UINavigationController 中,最后在特定 UIViewController 中。

    来自 UITabBarController 和 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];
    }
    

    【讨论】:

    • 我实际上确实在我的根视图控制器中使用了 UITabBarController 作为实例变量,因为您不应该在 6.0 之前的 iOS 版本中继承 UITabBarController 并且我需要支持回 iOS 5.0。
    • @micmdk 我也有同样的问题。请告知如何解决,我已经复制了您的代码并放入自定义导航控制器和 customUabbarController 并将其他视图控制器设置为 Brian's Guided。但仍然没有得到固定的纵向视图控制器。
    • @Brian 我也有同样的问题。请告知如何解决,我已复制您的代码并放入自定义导航控制器和 customUabbarController 并将其他视图控制器设置为 Micmdk 的引导。但仍然没有在 ios8 中获得固定的纵向视图控制器。
    【解决方案5】:

    我想对我自己的问题给出部分答案。我发现以下代码行,在我的第三个 UIViewControllerviewWillAppear 方法中使用:

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

    但我不太喜欢这种解决方案。它使用一种技巧来分配只读属性,根据 Apple 文档,该属性表示设备的 physical 方向。这就像告诉 iPhone 跳转到用户手中的正确方向。

    我很想把它留在我的应用程序中,因为它很简单。但这感觉不对,所以我想把这个问题留给一个干净的解决方案。

    【讨论】:

    • 如果您将应用留在那里,它很可能会被拒绝。我也在寻找解决方案,但看起来这是不可能的。我能找到的只是这个问题的答案:stackoverflow.com/questions/7196300/… 它有点工作,但由于某种原因,它破坏了我的一个视图控制器中的按钮触摸处理
    • 我抓住机会提交给了 Apple。仍在等待审核...我会及时通知您。
    • Apple 刚刚通过这个 hack 批准了我的应用程序,是的!我希望它不会在未来的 iOS 更新中适得其反。与此同时,我仍然愿意为干净的解决方案提供建议!
    • @HaraldBögeholz Grt 建议。但是当我在使用 ARC 的新 xCode 中工作时,我无法使用 ans 。我收到“ARC PerformSelector 不允许将 NSInteger (aka int) 转换为 id 可能会导致泄漏,因为它的选择器未知”我该如何解决这个问题?如果我们有解决方案,我真的很感激你。当我因为某些泄漏数据而关闭 ARC 以获得特定视图而不是应用程序崩溃时。请给我建议
    • 使用私有 API 永远不是解决方案。
    【解决方案6】:

    这是我在 ios 6.0 中用于方向支持的

    -(BOOL)shouldAutorotate{
        return YES;
    }  
    
     - (NSUInteger)supportedInterfaceOrientations{
        return UIInterfaceOrientationMaskAll;
    }
    
    
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{  
      return UIInterfaceOrientationPortrait;
    }
    

    【讨论】:

      【解决方案7】:

      因为这是一个高度关注的线程。我想我会添加我认为最简单的答案。这适用于 ios8 及更高版本

      -(BOOL)shouldAutorotate
      {
          return YES;
      }
      

      -(UIInterfaceOrientationMask)supportedInterfaceOrientations{
      return UIInterfaceOrientationMaskPortrait;
      }
      

      就是这样。享受吧!

      哦,我的 ViewController 嵌入在导航控制器中,我不需要以任何方式对其进行子类化或配置。

      【讨论】:

        【解决方案8】:

        这可能并不适合所有人,但对我来说非常有用。而不是实现...

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

        在我所有控制器的 viewWillAppear 中,我决定通过覆盖 UINavigationControllerDelegate 方法 navigationController:willShowViewController:animated: 将这个过程集中在我的 UINavigationController 子类中

        - (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;
            }
        }
        

        我发现从导航堆栈中弹出 VC 时不会调用此委托方法。这对我来说不是问题,因为我正在处理 UINavigationController 子类中的后退功能,这样我就可以为特定的 VC 设置正确的导航栏按钮和操作......

        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(...) {
            ...
        }
        

        这是我的back 方法处理将 VC 从堆栈中弹出并设置适当的旋转首选项的样子...

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

        所以,如您所见,基本上所有发生的事情都是我首先设置两个属性...

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

        navigationController:willShowViewController:animated: 中,它将确定即将呈现的VC 中支持的方向。弹出 VC 时,我的自定义“返回”方法被调用,然后我将 isLandscapeOK 设置为通过 previousVCLandscapeOK 值存储的内容。

        正如我所说,这可能不适用于所有人,但它对我来说非常有用,而且我不必担心向我的每个视图控制器添加代码,我能够将其全部集中在 UINavigationController 子类中.

        希望这对我有帮助。 谢谢,杰里米。

        【讨论】:

          【解决方案9】:

          转到您的 Info.plist 文件并进行更改

          【讨论】:

            【解决方案10】:

            您只想强制 iOS 6 应用肖像,然后您可以在方法下方添加到 UIViewController 子类

            - (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;
                }
            }
            

            【讨论】:

            • 问题是除非那个视图是根视图(它不在这个问题中),否则 shouldAutorotate 不会被调用。
            • 无论出于何种原因,NSUInteger 的返回类型都会引发警告,尽管它是正确的 var 类型。如果您有强迫症,请改用UIInterfaceOrientationMask
            • 此外,该解决方案非常适合我拥有的模态视图控制器(相机视图),而无需继承 UINavigationController。我猜模态是独立处理的。与原始问题无关,但有用的信息有利于设计。
            【解决方案11】:

            我想让我的所有 VC 都锁定为纵向,除了一个。这对我有用。

            1. 在 plist 文件中添加对所有方向的支持。
            2. 在根视图控制器中,检测窗口顶部的视图控制器类型,并在 supportedInterfaceOrientations 方法中相应地设置应用程序的方向。例如,我需要我的应用程序仅在 webview 位于堆栈顶部时旋转。这是我在 rootVC 中添加的内容:

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

            【讨论】:

            • Trunal,这已经有几个月的历史了,但您能否详细说明如何检测顶部的视图控制器类型。具体来说,我不确定您在[[Utils getAppDelegate] appNavigationController] 那里要做什么。 Utils 我猜你有一些课程,但我不知道你在其中做什么。任何帮助都会很棒!
            【解决方案12】:

            我没有足够的声望来回答@micmdk 回复下的@Ram S 问题,所以我将在此处添加我的注释。

            当你使用UITabbarController时,尝试将self.viewControllers.lastObject改成 @micmdk 的 self.selectedViewController 代码如下:

            - (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];
            }
            

            【讨论】:

              【解决方案13】:

              您可以在您的所有视图控制器类中实现和覆盖 shouldAutorotate() 和supportedInterfaceOrientations var,这些类应该以不同于您应用的 PLIST 中定义的方向呈现。

              但是,在一个重要的用户界面中,您可能会遇到将其添加到数十个类中的问题,并且您不想将所有这些类都设为支持它的几个常见的子类(MyBaseTableViewController、MyBaseNavigationController 和 MyBaseTabBarController)。

              由于您无法直接覆盖 UIViewController 上的那些方法/变量,您可以在其子类上执行此操作,这些子类通常是您的基类,如 UITableViewController、UINavigationController 和 UITabBarController。

              所以你可以实现一些扩展并且仍然设置 MyPreciousViewController 以不同于其他所有的方向显示,比如这个 Swift 4 代码 sn-p:

              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]
                  }
              }
              

              【讨论】:

                【解决方案14】:

                我已经解决了同样的问题。

                如果您使用UINavigationController 推送视图控制器,则必须设置以下方法。

                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
                    }
                }
                

                代替LoginViewController 使用你想要显示的UIViewController。就我而言,我想在Portrait 模式下显示LoginViewController,而在landscape 模式下显示ViewControllers

                【讨论】:

                  【解决方案15】:

                  请使用以下方法解决此问题

                  - (NSUInteger)supportedInterfaceOrientations
                  {
                      return UIInterfaceOrientationMaskPortrait;
                  }
                  

                  只返回你想要的方向!!!

                  【讨论】:

                  • 正如我所写的那样,我做到了。这可以防止我的视图控制器旋转,但是当我返回它时,它确实不会强制它回到唯一支持的方向。
                  • 这对我不起作用。即使将它包含在我的视图控制器中,视图仍然会旋转。什么给了?
                  • 为什么不赞成 maxfiresolutions?它对我有用,@Flo 给出的相同方法得到了很多支持。
                  猜你喜欢
                  • 2013-02-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-30
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-09-02
                  相关资源
                  最近更新 更多