子视图控制器旋转而父视图控制器不旋转

26

我试图做什么:

由父视图控制器管理的不应该旋转的父视图。

由子视图控制器管理的应该旋转到所有方向的子视图。


输入图像描述

输入图像描述


我尝试过的:

ParentViewController

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

override func shouldAutorotate() -> Bool {
    return true
}

fun addChildViewController() {
    let storyBoard = UIStoryboard(name: "Main", bundle: nil)
    self.childViewController = storyBoard("Child View Controller") as? ChildViewController
    self .addChildViewController(self.childViewController!)
    self.childViewController! .didMoveToParentViewController(self)
    self.view .addSubview(self.childViewController!.view)
    self.childViewController!.view.frame = CGRectMake (40, 200, 400, 250)
}

子视图控制器

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

override func shouldAutorotate() -> Bool {
    return true
}

Xcode 部署信息中支持的方向已设置为四个。

我的问题:

没有视图旋转。如果我将父视图的旋转设置为所有方向,那么所有视图都会一起旋转。所以要么全部旋转,要么不旋转。


更新

当我尝试为 UIDeviceOrientationDidChangeNotification 添加观察器,并使用 UIDevice.currentDevice().orientation 用 CGAffaineTransformMakeRotate 旋转 childView 时,我得到了期望的结果。然而,例如,如果我旋转到横向模式,并尝试下拉通知中心(或者如果我收到系统通知),通知中心仍处于纵向模式(因为应用程序在本地仍然保持为纵向模式),则旋转的 childView 会旋转回到纵向模式,以符合状态栏/通知/通知中心的方向。

原生 iOS 相机应用 是我可以提供的最佳示例。主画布不会旋转成不同的方向,但是在幕后,状态栏会旋转以符合设备方向。此外,子视图会旋转自身以符合不同的方向。我正在尝试实现这种行为...


1
不确定这是否可行,只是一个想法。我肯定会将应用程序设置为仅支持一种(纵向)方向,然后尝试检测设备的旋转(而不是屏幕),并通过手动旋转屏幕中间的视图来响应它。我猜 viewWillTransition(to:with:) 不起作用,所以你需要找到一个合适的解决方案。 - Andrej
你的意思是使用类似CGAffineTransform这样的东西进行旋转吗? - Gizmodo
1
没错,就是CGAffineTransform。你遇到了一个有趣的情况。很想帮忙并尝试编写一些代码来解决这个问题,但恐怕我会非常忙,所以我不能在这里亲自动手。 - Andrej
好的,我把CGAffineTransform旋转方法作为备选方案Z。我很惊讶上面的代码为什么不起作用。它只是没有将“shouldAutorotate()”传递给视图控制器。 - Gizmodo
1
请注意,由于iOS 8中自动旋转的底层实现发生了变化,向应用程序窗口添加子视图可能不再是一种有效的技术:https://developer.apple.com/library/ios/qa/qa1890/_index.html - jamesk
显示剩余2条评论
3个回答

13
相机应用程序中实现的效果类似于同时使用以下技术的组合: 技术说明解释了自动旋转的基本实现方式,涉及直接向应用程序的窗口应用变换。
当系统决定界面需要旋转时,自动旋转通过将旋转变换应用于应用程序窗口来实现。然后,窗口会根据新的方向调整其边界,并通过调用每个视图控制器和呈现控制器的 viewWillTransitionToSize:withTransitionCoordinator:方法沿着视图控制器层次结构传播此更改。
请注意,相机应用程序不会选择退出自动旋转:在使用相机应用程序时,通知中心和控制中心的方向会反映设备的方向。只有相机应用程序中的特定视图似乎选择退出自动旋转。实际上,this answer 建议这类应用程序通常利用 AVFoundation 类提供的 videoGravity 和 videoOrientation 属性,如 AVCaptureVideoPreviewLayerAVCaptureConnection
你应该采取的方法取决于你是想停止父视图旋转还是想停止容器视图控制器(例如 UINavigationController)旋转(即锁定设备方向)。
如果是前者,使用技术说明中描述的技术来修复父视图的方向,并且(对于iOS 9)将旋转的子视图包装在UIStackView中,并使用axis属性在设备旋转时重新排列子视图,或者(对于iOS 8)在设备旋转时手动旋转子视图(关于这一点,请参见this answerthis sample code)。
如果是后者,则您正在采取正确的方法。有关示例代码,请参见this answer。您需要共享代码和更多有关您的视图控制器层次结构的信息,以便我们诊断与通知中心相关的怪癖。
补充说明:调用shouldAutorotatesupportedInterfaceOrientations未被传播到子视图控制器的原因在Technical Q&A QA1688中解释了。
从iOS 6开始,仅最顶层的视图控制器(以及UIApplication对象)参与决定是否响应设备方向的更改而旋转。往往情况下,显示您内容的视图控制器是UINavigationControllerUITabBarController或自定义容器视图控制器的子级。您可能需要对容器视图控制器进行子类化,以修改其supportedInterfaceOrientationsshouldAutorotate方法返回的值。

在Swift中,您可以使用扩展覆盖这些方法


非常好的回复!我仍在阅读链接。正如我在下面提到的,我在使用viewWillTransitionToSize:withTransitionCoordinator:时遇到了问题,因为我希望motherView不旋转,而childView旋转。如果motherView根本不旋转,则viewWillTransitionToSize:withTransitionCoordinator:将不会在其自身内部调用。 - Gizmodo
这里的技巧是:不要从 shouldAutorotate 返回 false。使用技术说明中展示的技巧来防止 motherView 旋转,这不会干扰 viewWillTransitionToSize:,然后使用后者(或 viewWillLayoutSubviews)相应地旋转您的子视图。 - jamesk
1
让我们来帮忙!哪里出了问题? - jamesk
到目前为止,我使用了viewWillTransitionToSize:和UIDevice.currentDevice().orientation与UIDeviceOrientationDidChangeNotification KVO的复杂方法。使用viewWillTransitionToSize:停止旋转mainView。使用UIDevice.currentDevice().orientation与UIDeviceOrientationDidChangeNotification KVO来使用CGAffineTransformMakeRotation旋转childView。 - Gizmodo
这对我似乎不起作用,就像在http://stackoverflow.com/questions/42778355/why-am-i-seeing-a-glitch-in-the-counter-rotation-animation中描述的那样。有什么想法吗? - Kartick Vaddadi
显示剩余3条评论

3
来自iOS开发者库的技术问答:Technical Q&A 自动旋转是通过将旋转变换应用于应用程序的窗口来实现的,当系统确定界面必须旋转时。然后,窗口会调整其边界以适应新方向,并通过调用每个视图控制器和表示控制器的-viewWillTransitionToSize:withTransitionCoordinator:方法将此更改向下传播到视图控制器层次结构。该方法的调用提供了一个过渡协调器对象,其中包含窗口当前变换和新变换之间的差异,可以通过向过渡协调器发送-targetTransform消息来检索。您的视图控制器或表示控制器可以得出适当的反向变换并将其应用于目标视图。这消除了窗口变换对该特定视图的影响,并使该视图看起来在其当前方向上保持不动,而其他界面正常旋转。
即,如果您有一个名为noRotatingView的视图。
override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    noRotatingView.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))
}

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({ (UIViewControllerTransitionCoordinatorContext) -> Void in

        let deltaTransform = coordinator.targetTransform()
        let deltaAngle = atan2f(Float(deltaTransform.b), Float(deltaTransform.a))

        var currentRotation = self.noRotatingView.layer.valueForKeyPath("transform.rotation.z")?.floatValue

        // Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
        currentRotation = currentRotation! + (-1 * Float(deltaAngle)) + 0.0001
        self.noRotatingView.layer.setValue(currentRotation, forKeyPath:"transform.rotation.z")

        }) { (UIViewControllerTransitionCoordinatorContext) -> Void in

            var currentTransform = self.noRotatingView.transform;
            currentTransform.a = round(currentTransform.a);
            currentTransform.b = round(currentTransform.b);
            currentTransform.c = round(currentTransform.c);
            currentTransform.d = round(currentTransform.d);
            self.noRotatingView.transform = currentTransform;
    }
}

我已经尝试在https://github.com/rishi420/TestCameraRotation上创建了一个演示项目。

你的回答做得很好!看了你的示例代码后,我仍然遇到了一个主要问题,即使我在查看苹果的技术文档时也是如此:MotherView不会旋转,ChildView会旋转。如果包含视图根本不旋转,则不会调用viewWillTransitionToSize方法。这就是我仍然卡住的地方... - Gizmodo
@gizmodo 我认为iOS相机应用程序中的主视图会旋转,因为通知中心和控制中心会旋转。如果您想锁定主视图,可以查看RotateButton - Warif Akhand Rishi
1
在这一点上,将TestCameraRotation和RotateButton组合起来是最合理的。1. 将MainView放入另一个视图中,并使用viewWillTransitionToSize停止其旋转。2. 使用设备方向KVO来旋转childView。 - Gizmodo
这似乎对我不起作用,就像http://stackoverflow.com/questions/42778355/why-am-i-seeing-a-glitch-in-the-counter-rotation-animation中描述的那样。 你有什么想法?@Gizmodo - Kartick Vaddadi

2

在与苹果公司的多次沟通中,他们一直指向同一个链接Technical Q&A QA1890。为了解决这个问题,我必须按照以下方式进行操作:

MotherViewController

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

        coordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext) -> Void in
            let deltaTransform: CGAffineTransform = coordinator.targetTransform()
            let deltaAngle: CGFloat = atan2(deltaTransform.b, deltaTransform.a)
            var currentRotation: CGFloat = self.mainView.layer.valueForKeyPath("transform.rotation.z") as! CGFloat

            // Adding a small value to the rotation angle forces the animation to occur in a the desired direction, preventing an issue where the view would appear to rotate 2PI radians during a rotation from LandscapeRight -> LandscapeLeft.
            currentRotation += -1 * deltaAngle + 0.0001
            self.mainView.layer.setValue(currentRotation, forKeyPath: "transform.rotation.z")

            }, completion: {(
                context: UIViewControllerTransitionCoordinatorContext) -> Void in
                // Integralize the transform to undo the extra 0.0001 added to the rotation angle.
                var currentTransform: CGAffineTransform = self.mainView.transform
                currentTransform.a = round(currentTransform.a)
                currentTransform.b = round(currentTransform.b)
                currentTransform.c = round(currentTransform.c)
                currentTransform.d = round(currentTransform.d)
                self.mainView.transform = currentTransform
        })
    }

MainViewController

NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name:UIDeviceOrientationDidChangeNotification, object: nil)

func orientationChanged ( notification: NSNotification){
        switch UIDevice.currentDevice().orientation {
        case .Portrait:
            self.rotateChildViewsForOrientation(0)
        case .PortraitUpsideDown:
            self.rotateChildViewsForOrientation(0)
        case .LandscapeLeft:
            self.rotateChildViewsForOrientation(90)
        case .LandscapeRight:
            self.rotateChildViewsForOrientation(-90)
        default:
            print ("Default")
                }
    }

这似乎是在保持主视图静态的同时旋转子视图。此外,当通知到来时,应用程序似乎知道正确的方向(因为母视图实际上在幕后旋转)。

特别感谢 jameskWarif Akhand Rishi 的所有帮助!


这对我似乎不起作用,就像http://stackoverflow.com/questions/42778355/why-am-i-seeing-a-glitch-in-the-counter-rotation-animation所描述的那样。有什么想法吗? - Kartick Vaddadi
你能详细说明一下这个答案吗?rotateChildViewsForOrientation是什么?没有该函数的声明。 - Lance Samaria

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