【问题标题】:Child View Controller to Rotate While Parent View Controller Does Not子视图控制器在父视图控制器不旋转时旋转
【发布时间】:2016-07-31 14:28:55
【问题描述】:

我要做什么:

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

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



我的尝试:

父视图控制器

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

ChildViewController

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

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

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

我得到了什么:

视图没有旋转。如果我将父级的旋转设置为全部,则所有视图一起旋转。所以要么全有,要么全无。


更新

当我尝试为 UIDeviceOrientationDidChangeNotification 放置一个观察者并使用 UIDevice.currentDevice().orientation 使用 CGAffaineTransformMakeRotate 旋转 childView 时,我得到了想要的结果。但是,即,如果我旋转到横向,并尝试下拉通知中心(或者如果我收到系统通知),它仍然是纵向的(因为应用程序本身仍然是纵向的),旋转 childView 旋转回纵向以尊重状态栏/通知/通知中心。

Stock iOS Camera App 是我能展示的最佳示例。主画布不会旋转到不同的方向,但在幕后,状态栏会根据设备旋转进行旋转。此外,子视图在它们内部旋转以尊重不同的方向。我正在尝试实现这种行为....

【问题讨论】:

  • 不确定这是否可行,把它当作一个想法。我肯定会将应用程序设置为仅支持一个(纵向)方向,然后我会尝试检测设备(而不是屏幕)的旋转并通过手动旋转屏幕中间的视图来响应它。我猜viewWillTransition(to:with:) 不起作用,所以你需要找到一个合适的解决方案。
  • 您的意思是使用 CGAffineTransform 之类的东西进行旋转?
  • 是的,正是 CGAffineTransform。你有一个有趣的情况。很乐意提供帮助并实际尝试编写一些代码来解决这个问题,但我担心我会很忙,所以我不能在这里弄脏我的手。
  • 好吧,我将 CGAffineTransform 旋转方法作为 Z 计划。我很惊讶上面的代码无法正常工作。它只是不会将“shouldAutorotate()”传递给视图控制器。
  • 请注意,由于 iOS 8 中自动旋转的底层实现发生了变化,在应用程序的窗口中添加子视图可能不再是一种有效的技术:developer.apple.com/library/ios/qa/qa1890/_index.html

标签: swift xcode uiviewcontroller landscape-portrait childviewcontroller


【解决方案1】:

在相机应用中实现的效果类似于使用以下组合:

技术说明解释了自动旋转的底层实现涉及将变换直接应用于应用程序的窗口:

当系统确定界面必须旋转时,通过对应用程序的窗口应用旋转变换来实现自动旋转。然后,窗口会针对新方向调整其边界,并通过调用每个视图控制器和表示控制器的 viewWillTransitionToSize:withTransitionCoordinator: 方法将这种更改向下传播到视图控制器层次结构。

请注意,相机应用选择退出自动旋转:使用相机应用时,通知中心和控制中心的方向反映设备方向。只有相机应用程序中的特定视图似乎选择退出自动旋转。事实上,this answer 表明此类应用程序通常使用 AVFoundation 类提供的 videoGravityvideoOrientation 属性,例如 AVCaptureVideoPreviewLayerAVCaptureConnection

您应该采取的方法取决于您是只想阻止父视图旋转,还是要阻止 UINavigationController 等容器视图控制器旋转(即锁定设备方向)。

如果是前者,请使用技术说明中描述的技术来固定父视图的方向,并且(对于 iOS 9)将旋转子视图包装在 UIStackView 中并使用axis 属性重新排列设备旋转时的子视图, 或(对于 iOS 8)手动旋转设备旋转的子视图(具体参见 this answerthis sample code)。

如果是后者,那么您采用的方法是正确的。有关示例代码,请参阅this answer。您需要分享您的代码和有关您的视图控制器层次结构的更多信息,以便我们诊断涉及通知中心的怪癖。

附录Technical Q&A QA1688 中解释了对 shouldAutorotatesupportedInterfaceOrientations 的调用没有传播到子视图控制器的原因:

从 iOS 6 开始,只有最顶层的视图控制器(与 UIApplication 对象一起)参与决定是否旋转以响应设备方向的变化。通常,显示内容的视图控制器是UINavigationControllerUITabBarController 或自定义容器视图控制器的子级。您可能会发现自己需要对容器视图控制器进行子类化,以便修改其 supportedInterfaceOrientationsshouldAutorotate 方法返回的值。

在 Swift 中,您可以override those methods by using extensions

【讨论】:

  • 很好的回复!我仍在阅读链接。正如我在下面提到的,我在使用 viewWillTransitionToSize:withTransitionCoordinator: 时遇到了问题,因为我希望在 childView 旋转时,motherView 不旋转。如果 motherView 根本不旋转, viewWillTransitionToSize:withTransitionCoordinator: 将不会在它自身内部被调用。
  • 诀窍是:不要从shouldAutorotate 返回false。使用技术说明中显示的技术来防止 MotherView 旋转——这不会干扰viewWillTransitionToSize:——并使用后者(或viewWillLayoutSubviews)相应地旋转您的子视图。
  • 让我们帮忙!什么行为不端?
  • 嗯,您的链接非常有帮助。我知道这是可以使用 stackView 和 Autolayout 实现的;然而, viewWillTransitionToSize: 方法似乎让这个特定项目的事情变得更干净。
  • 回到开头:我想在 childView 旋转时保持 mainView 静态。毕竟,当收到通知时,状态栏必须处于正确的方向。参考iOS Camera App,主界面静止,flash图标旋转。我需要确切的行为。如果要使用 viewWillTransitionToSize: 方法,我如何在 mainViewController 中使用它,如果那是我试图停止旋转的视图?另外,我确实希望 childView 旋转。
【解决方案2】:

来自 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做一个演示项目

【讨论】:

  • 您的回答做得很好!查看您的示例代码,即使在查看 Apple 的技术文档时,我仍然遇到的主要问题是:MotherView 不旋转,ChildView 旋转。如果包含视图根本不旋转,则不会调用 viewWillTransitionToSize 方法。这就是我仍然卡住的地方......
  • @gizmodo 我认为母亲视图在 iOS 相机应用程序中旋转。因为通知中心和控制中心旋转。如果要锁定妈妈视图可以看RotateButton
  • TestCameraRotation + RotateButton 的组合在这一点上最有意义。 1. 将 MainView 放在另一个视图中,并使用 viewWillTransitionToSize 阻止它旋转。 1.使用设备方向KVO旋转childView。
  • 这似乎对我不起作用,如stackoverflow.com/questions/42778355/… 所述,知道为什么吗? @Gizmodo
【解决方案3】:

在他们自己与苹果公司多次来回后,他们指向同一个链接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
        })
    }

主视图控制器

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 的所有帮助!

【讨论】:

  • 这似乎对我不起作用,如stackoverflow.com/questions/42778355/… 中所述,知道为什么吗?
  • 你能详细说明这个答案吗?什么是rotateChildViewsForOrientation?没有函数声明
猜你喜欢
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多