如何在关闭模态视图控制器时保持呈现视图控制器的方向?

33

我正在开发一个应用程序,除了一个视图控制器外,我需要所有的视图控制器都是纵向显示。而这个特殊的视图控制器需要能够旋转到手机所处的任何方向。

为了实现这一点,我使用模态呈现它(不嵌入在 NavigationController 中)。

因此,我的结构例如如下:

  • window - 纵向显示
    • root view controller(UINavigationController - 纵向显示)
      • home view controller(UIViewController - 纵向显示)
        • details view controller(UIViewController - 纵向显示)
        • .
        • .
        • .
        • modal view controller(UIVIewController - 所有方向)

现在,每当我以横向位置关闭我的模态视图控制器时,即使它不支持该方向,我的父视图控制器也会旋转。

应用程序中的所有 UIViewControllersUINavigaionControllers 继承自相同的通用类,其中包含这些方法的实现:

override func supportedInterfaceOrientations() -> Int
{
    return Int(UIInterfaceOrientationMask.Portrait.toRaw())
}

我的模态视图控制器再次覆盖了这个方法,代码如下:

override func supportedInterfaceOrientations() -> Int
{
    return Int(UIInterfaceOrientationMask.All.toRaw())
}

更新1

看起来这只发生在iOS 8测试版上。 是否有人知道关于视图控制器旋转是否有所改变,还是这只是测试版中的一个错误?


我曾经遇到过类似的问题,并且已经有了补丁,可以参考https://dev59.com/3oPba4cB1Zd3GeqPp0Sh - Jageen
9个回答

20
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ([self.window.rootViewController.presentedViewController isKindOfClass: [SecondViewController class]])
{
    SecondViewController *secondController = (SecondViewController *) self.window.rootViewController.presentedViewController;

    if (secondController.isPresented)
        return UIInterfaceOrientationMaskAll;
    else return UIInterfaceOrientationMaskPortrait;
}
else return UIInterfaceOrientationMaskPortrait;
}

对于Swift语言

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow) -> Int {

    if self.window?.rootViewController?.presentedViewController? is SecondViewController {

        let secondController = self.window!.rootViewController.presentedViewController as SecondViewController

        if secondController.isPresented {
            return Int(UIInterfaceOrientationMask.All.toRaw());
        } else {
            return Int(UIInterfaceOrientationMask.Portrait.toRaw());
        }
    } else {
        return Int(UIInterfaceOrientationMask.Portrait.toRaw());
    }

}

欲了解更多详细信息,请查看此链接


你已经有一段时间没有回答了,但今天我又遇到了这个问题。你的解决方案确实有效!谢谢! - Mihai Fratu
这对我不起作用,而且UIViewController上没有isPresented属性。 - Ric Santos
@RicSantos 请查看上面答案中附带的链接,以获取完整的详细信息。在视图控制器中添加了一个属性。 - ZaEeM ZaFaR
这对于UI页面视图控制器不起作用。我在我的页面视图控制器中有2个视图控制器(A,B)。使用此代码时,所有断点都正确触发,但当我从B(横向)移动到A(纵向)时,仍然会出现横向方向。 - nr5
1
链接目前无法访问(500)。请在您的回答中包含相关细节。幸运的是,存档目前可以访问:https://web.archive.org/web/20170628051957/http://swiftiostutorials.com/ios-orientations-landscape-orientation-one-view-controller/ - Fls'Zen
@Fls'Zen 感谢您提供存档页面。这是上述实现的 Git 源代码的活动链接。也将其添加到答案中。 - ZaEeM ZaFaR

7

我使用一个应用程序时出现了同样的问题,经过几天的尝试,我想出了一个解决方法,虽然不是很好,但目前可以使用。我在appdelegate中使用代理方法application:supportedInterfaceOrientationsForWindow:

我创建了一个测试项目,并将其放在github上(包括展示结果的GIF...)

//注意:它不是用swift写的,但我希望这能帮到你们。


我曾经遇到过类似的问题,并且像你一样解决了它,但是出现了另一个问题,在这里解释:https://dev59.com/3oPba4cB1Zd3GeqPp0Sh - Jageen
在这个主题上花费了一整天之后,我在你的回答中找到了解决方案。对我来说很有道理,在我的控制器中完美地运行。非常感谢。 - Fabrizio

5
经过多次试验,我确信这是iOS 8的一个“特性”。
如果你仔细考虑一下,这是有道理的,因为它已经来了很长时间了。
在iOS 4中,可以强制应用程序在选项卡控制器和导航控制器中更改视图控制器时旋转,以及在呈现/解除控制器时旋转。
然后在iOS 6中,除了在呈现/解除视图控制器时(如我在许多答案中解释的那样,例如这个),不可能强制应用程序旋转。
现在,在iOS 8中,我推测将无法强制应用程序旋���(除了在启动时)。它可以优先选择某个方向,以使其保持在该方向,但它不能强制应用程序进入该方向。
相反,您的视图控制器应该“适应”。有几个关注“适应”的WWDC 2014视频,现在我开��明白这是为什么这很重要的原因之一。
编辑:在种子4中,看起来这个功能(在呈现和解除时强制旋转)正在回归!

只是举个例子,我发现 Store Kit 的警报现在在横屏模式下可以正常工作了。它们在横屏模式下无法正常工作是你需要在使用 Store Kit 之前强制将应用程序切换到纵向模式的原因之一。现在这个原因已经不存在了。这表明 Apple 已经在审查框架并消除了强制旋转的原因,这反过来又表明这种做法可能已经不再可行。 - matt
嗯...这似乎很奇怪。我的意思是,这意味着我的应用程序必须能够在横屏和竖屏下显示所有屏幕?因为在我上面的例子中,我的应用程序仅支持纵向,但有一个以模态方式呈现的视图,即照片库。在父VC上使用首选方向似乎在我的观点中无法正常工作,因为以模态方式呈现的子VC会在隐藏时旋转我的父VC。 - Mihai Fratu
1
你是如何“强制旋转”的?在一个支持横屏和竖屏的模态视图控制器被解散并返回到只支持竖屏的视图控制器后,我仍然遇到了问题。 - johosher
@johosher 我仍然看到一些边缘情况,所以请确保将任何可重现的情况打包成错误报告并报告!如果iOS 8在没有解决这个问题的情况下最终确定,那将是糟糕的。 - matt
2
@matt 你有没有想到在iOS 8中强制旋转视图控制器的方法? - Saikiran Komirishetty

3
我们部署了一个应用,其中包含一个横向控制器,它呈现一个仅支持纵向的视图控制器。这个应用在iOS 8上经过了Apple的审核。我们只是覆盖了supportedInterfaceOrientations。
值得注意的是,我们发现beta版本3、4和5之间存在很多差异。最终,在更新我们的应用程序以适配iOS 8之前,我们不得不等到GM版。
在iOS 8中,您需要非常小心,以确保您不会做到这一点:
[self dismissViewControllerAnimated:YES completion:nil]
[self presentViewController:vc animated:YES completion:nil]

如果出去的视图控制器是竖屏的,而进来的视图控制器是横屏的,一些视图框架可能会变得非常混乱。在解散调用的完成块中呈现进入的视图控制器。
[self dismissViewControllerAnimated:YES completion:^{
    [self presentViewController:vc animated:YES completion:nil]
}];

vc 是什么?原始的展示视图控制器吗? - bcattle
Vc是您想要呈现的新控制器。如果它是原始的呈现vc,您将尝试在自身上呈现自身。 - Airsource Ltd

3

非常棒的问题和答案,由@ZaEeM ZaFaR提供!将他的答案与这个结合起来,我找到了一个更好、更通用的解决方案。

第一个答案的缺点是你必须在允许旋转的每个视图控制器中管理变量isPresented。此外,在每个允许旋转的vc中扩展检查和强制转换也是必要的。

第二个答案的缺点是它不起作用;在解除显示的vc时,它也会旋转呈现的vc。

这个解决方案允许在所有放置了canRotate(){}的vc中旋转,并且不旋转呈现的vc。

Swift 3:
在AppDelegate.swift中:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
        if (rootViewController.responds(to: Selector(("canRotate")))) {
            // Unlock landscape view orientations for this view controller if it is not currently being dismissed
            if !rootViewController.isBeingDismissed{
                return .allButUpsideDown
            }
        }
    }
    
    // Only allow portrait (standard behaviour)
    return .portrait
}

private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
    if (rootViewController == nil) {
        return nil
    }
    if (rootViewController.isKind(of: UITabBarController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
    } else if (rootViewController.isKind(of: UINavigationController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
    } else if (rootViewController.presentedViewController != nil) {
        return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
    }
    return rootViewController
}

在每个需要允许旋转的视图控制器中:
func canRotate(){}

1
这个运行非常出色。对于Swift 4用户唯一需要注意的是,他们需要为canRotate() 函数包含 @objc ,即:*@objc func canRotate(){}*。 - Chewie The Chorkie

3

这实际上更容易,而且可以在不添加任何其他属性的情况下完成(以下是使用AVPlayerViewController的示例):

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        if ([self.window.rootViewController.presentedViewController isKindOfClass: [AVPlayerViewController class]])
            return self.window.rootViewController.presentedViewController.isBeingDismissed ? 
            UIInterfaceOrientationMaskPortrait : UIInterfaceOrientationMaskAll;
        else
            return UIInterfaceOrientationMaskPortrait;
    } else {
        return UIInterfaceOrientationMaskAll;
    }
}

1
在根视图控制器中尝试添加以下内容:

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

对我有用。


0
Swift 3.0或更高版本, 只需检查所呈现的视图控制器的“isBeingDismissed”属性。 以下是示例代码,这将在呈现的视图控制器被解除后立即将呈现的视图控制器旋转为纵向模式。
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController)
{
  if rootViewController.canRotateVC == true
  {
    if baseVC.isBeingDismissed == false
    {
      return .allButUpsideDown
    }
  }
}

  return .portrait}

你可以通过以下代码获取 topController:
  private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController?{
if (rootViewController == nil) { return nil }if (rootViewController.isKind(of: (UITabBarController).self))
{
  return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
}
else if (rootViewController.isKind(of:(UINavigationController).self))
{
  return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
}
else if (rootViewController.presentedViewController != nil)
{
  return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController }

我尝试了您的方法,但很遗憾当被呈现的视图控制器被解散时,应用程序:supportedInterfaceOrientationsForWindow:方法根本没有被调用。 - Jaime S

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接