强制iOS视图不旋转,同时仍允许子视图旋转

9

我有一个带有子视图控制器的视图控制器。

tab bar controller
|
|
nav controller
|
|
UIPageViewController (should rotate)
|
|
A (Video Player) (shouldn't rotate)
|
|
B (Controls overlay) (should rotate)

应当始终强制 A 保持竖屏状态,但 B 应该允许自由旋转。

我知道 shouldAutorotate 适用于任何视图控制器及其子级,但有没有办法绕过这个问题?似乎我可以使用 shouldAutorotateToInterfaceOrientation,但在 iOS 8 中被阻止了。

我想让视频播放器保持静态(所以无论设备方向如何,水平视频始终是水平的),而控件层子视图叠加图层则允许自由旋转。

我正在使用 Swift。


你是从ViewControllerA推出ViewControllerB的吗? - Tomas Camin
我正在将它添加为子视图。self.view.addSubview(viewController.view) - switz
6个回答

22

我遇到了完全相同的问题,并很快发现有许多关于自动旋转的不良建议流传,特别是因为iOS 8处理它的方式与以前的版本不同。

首先,你不应该手动应用反向旋转或订阅UIDevice方向更改。进行反向旋转仍会导致不美观的动画效果,并且设备方向并不总是与界面方向相同。理想情况下,你希望摄像头预览保持真正静止,而你的应用程序UI则与状态栏方向和尺寸匹配,就像本地相机应用程序一样。

在iOS 8中进行方向更改时,窗口本身会旋转而不是它包含的视图。你可以将多个视图控制器的视图添加到单个UIWindow中,但只有rootViewController将有机会通过shouldAutorotate()做出响应。即使你在视图控制器级别上做出旋转决策,也是父窗口实际旋转,从而旋转其所有子视图(包括来自其他视图控制器的视图)。

解决方案是两个UIWindow叠加在一起,每个窗口都有自己的根视图控制器进行旋转(或不旋转)。大多数应用程序只有一个窗口,但你无法像任何其他UIView子类一样拥有两个窗口并重叠它们。

这里是一个工作的概念验证,我也将其放在了GitHub上。你的特殊情况稍微复杂一些,因为你有一堆包含视图控制器,但基本思路是相同的。我将在下面涉及一些具体要点。

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var cameraWindow: UIWindow!
    var interfaceWindow: UIWindow!

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
        let screenBounds = UIScreen.mainScreen().bounds
        let inset: CGFloat = fabs(screenBounds.width - screenBounds.height)

        cameraWindow = UIWindow(frame: screenBounds)
        cameraWindow.rootViewController = CameraViewController()
        cameraWindow.backgroundColor = UIColor.blackColor()
        cameraWindow.hidden = false

        interfaceWindow = UIWindow(frame: CGRectInset(screenBounds, -inset, -inset))
        interfaceWindow.rootViewController = InterfaceViewController()
        interfaceWindow.backgroundColor = UIColor.clearColor()
        interfaceWindow.opaque = false
        interfaceWindow.makeKeyAndVisible()

        return true
    }
}

interfaceWindow上设置负的内嵌值可以使其略微大于屏幕边界,从而有效地隐藏了您否则会看到的黑色矩形遮罩。通常您不会注意到,因为该遮罩会随窗口旋转,但由于相机窗口是固定的,所以在旋转期间角落处的遮罩变得可见。

class CameraViewController: UIViewController {
    override func shouldAutorotate() -> Bool {
        return false
    }
}

这里正是你所期望的,只需添加自己的AVCapturePreviewLayer设置即可。

class InterfaceViewController: UIViewController {
    var contentView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        contentView = UIView(frame: CGRectZero)
        contentView.backgroundColor = UIColor.clearColor()
        contentView.opaque = false

        view.backgroundColor = UIColor.clearColor()
        view.opaque = false
        view.addSubview(contentView)
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        let screenBounds = UIScreen.mainScreen().bounds
        let offset: CGFloat = fabs(screenBounds.width - screenBounds.height)

        view.frame = CGRectOffset(view.bounds, offset, offset)
        contentView.frame = view.bounds
    }

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

    override func shouldAutorotate() -> Bool {
        return true
    }
}
最后一个技巧是撤销我们对窗口应用的负插图效果,这可以通过将view偏移相同的量并将contentView视为主视图来实现。
对于您的应用程序,interfaceWindow.rootViewController将是您的选项卡栏控制器,其中又包含一个导航控制器等。所有这些视图在相机控制器出现时需要透明,以便相机窗口可以在其下显示。出于性能原因,您可以考虑保持它们不透明,并仅在相机实际使用时将所有内容设置为透明,并在相机未使用时设置相机窗口为hidden(同时关闭捕获会话)。
很抱歉发了一篇小说;我没有看到其他地方讨论过这个问题,我花了一段时间才弄清楚,希望可以帮助您和其他试图获得相同行为的人。即使是Apple的AVCam示例应用程序也没有处理得完全正确。 我发布的示例存储库还包括已经设置好相机的版本。祝你好运!

我尝试使用MainWindow来实现这个功能,其中包括在该窗口上使用PreviewView和一个UIViewController来处理顶部其他视图的旋转。在iOS 7之前它是有效的,但在iOS 7中出现了问题。我还没有尝试过iOS8。你有在iOS 7上测试过你的代码吗?使用两个窗口真是太棒了! - mahboudz
您是否期望FixedViewController有一种方式可以托管任何用户可交互的项,例如按钮或手势识别器? - mahboudz
这个可能无法直接在7中使用,但是进行一些修改后可能会奏效(我只针对8进行了测试)。FixedViewController上的用户交互通常正常工作,因为主视图会随着view.frame = CGRectOffset(view.bounds, offset, offset)这一行代码发生偏移(不过你仍然需要使用contentView来呈现实际内容)。 - jstn
我已经授予您赏金,因为您的答案非常详尽,看起来可行,并包含代码示例。我还没有机会在我的代码库上测试它,但一旦我这样做了,我将授予您答案。感谢您提供如此好的答案! - switz
@SeanRobinson159 如果你想调用这些方法(例如ShouldAutorotate()等),就必须在iPad上强制全屏。看一下这个主题:链接 - Csharpest
显示剩余3条评论

1
你可以尝试这个方法 - 如果你有Objective-C代码的Swift替代方案:
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
   if ()//Place your condition here like if A is visible
   {
     return UIInterfaceOrientationMaskPortrait;
   }
     return UIInterfaceOrientationMaskAll;
} 

1
您可以订阅旋转更改通知,并手动设置要旋转的子视图的旋转变换矩阵。

0
短答案:不,所有可见的控制器和视图一起旋转(或不旋转)。
长答案:
首先,您必须在根控制器中实现自动旋转决策函数;这可能意味着创建一个导航控制器子类。
您可以通过使父视图自动旋转来实现所需的行为,但是要手动将其旋转回来以显示未旋转的状态。
或者,您可以不进行自动旋转,但是监听物理设备旋转的通知,并手动旋转您想要的任何视图,例如:Replicate camera app rotation to landscape IOS 6 iPhone 另外,请参阅:

How to force a UIViewController to Portrait orientation in iOS 6

shouldAutoRotate Method Not Called in iOS6

iOS6:supportedInterfaceOrientations不起作用(被调用但界面仍旋转)

如何在响应方向更改时实现UIViewController旋转?


0

我不确定,但我认为你可以为你的子视图创建一个自己的类,并覆盖shouldAutorotate方法等。这样它应该会覆盖父视图控制器中的shouldAutorotate


0

回答这个问题最简单、最直接的方法是查看苹果的AVCam示例代码。对我来说,关键部分是:

  1. 使用一个视图,其layerClassAVCaptureVideoPreviewLayer
  2. 在呈现视图时(基本上是viewWillAppear(_:)),将AVCaptureVideoPreviewLayer的连接的videoOrientation设置为与应用程序的statusBarOrientation匹配。
  3. viewWillTransitionToSize(_:withTransitionCoordinator:)中将videoOrientation设置为与UIDevice.currentDevice().orientation匹配。
  4. 启用自动旋转并支持所有界面方向。

我实现了jstn描述的背景窗口方法,它运行得相当不错,但现实情况是,它比必要的要复杂得多。AVCam非常好用,而且方法相对简单。


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