子视图控制器旋转而父视图控制器不旋转
Child View Controller to Rotate While Parent View Controller Does Not
我想做什么:
由父视图控制器管理的父视图不应旋转。
由子视图控制器管理的子视图应该旋转到所有方向。
我试过的:
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)
}
ChildViewController
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .All
}
override func shouldAutorotate() -> Bool {
return true
}
Xcode 部署信息中支持的方向设置为全部四个。
我得到的:
None 视图的旋转。如果我将父级的旋转设置为全部,则所有视图一起旋转。所以要么全有要么全无。
更新
当我尝试为 UIDeviceOrientationDidChangeNotification 放置一个观察者并使用 UIDevice.currentDevice().orientation 使用 CGAffaineTransformMakeRotate 旋转 childView 时,我得到了想要的结果。但是,即,如果我旋转到横向,并尝试拉下通知中心(或者如果我收到系统通知),它仍然处于纵向方向(因为应用程序本身仍然留在纵向),旋转 childView 旋转回纵向以纪念状态 bar/notification/notification 居中。
Stock iOS Camera App 是我能举出的最好的例子。主要 canvas 不会旋转到不同的方向,但状态栏在幕后会旋转以响应设备旋转。子视图也会在它们自身内旋转以适应不同的方向。我正在努力实现这种行为....
在相机应用中实现的效果类似于使用以下组合:
- Technical Q&A QA1890 中描述的防止视图旋转的技术;和
- 使用子 UIStackViews 在设备旋转时重新排列子视图(参见 SimpleExampleViewController class in the AdaptiveElements sample code shown in WWDC 2016 Session 233)。
技术说明解释了自动旋转的底层实现涉及将转换直接应用于应用程序的 window:
Autorotation is implemented by applying a rotation transform to the application's window when the system determines that the interface must rotate. The window then adjusts its bounds for the new orientation and propagates this change down through the view controller hierarchy via calls to each view controller's and presentation controller's viewWillTransitionToSize:withTransitionCoordinator:
method.
请注意,相机应用不会选择退出自动旋转:使用相机应用时,通知中心和控制中心的方向会反映设备方向。只有相机应用程序中的特定视图似乎选择退出自动旋转。事实上,this answer suggests that such apps typically utilise the videoGravity
and videoOrientation
properties provided by AVFoundation classes such as AVCaptureVideoPreviewLayer and AVCaptureConnection.
您应该采取的方法取决于您是只想阻止父视图旋转,还是想阻止容器视图控制器(例如 UINavigationController)旋转(即锁定设备方向)。
如果是前者,请使用技术说明中描述的技术来固定父视图的方向,并且(对于 iOS 9)将旋转子视图包装在 UIStackView 中并使用 axis
属性 在设备旋转时重新排列子视图,或(对于 iOS 8)在设备旋转时手动旋转子视图(参见 and this sample code)。
如果是后者,则说明您采用的方法是正确的。有关示例代码,请参阅 this answer。您需要共享您的代码和有关视图控制器层次结构的更多信息,以便我们诊断涉及通知中心的怪癖。
附录:Technical Q&A QA1688 中解释了对 shouldAutorotate
和 supportedInterfaceOrientations
的调用未传播到子视图控制器的原因:
Beginning with iOS 6, only the topmost view controller (alongside the UIApplication
object) participates in deciding whether to rotate in response to a change of the device's orientation. More often than not, the view controller displaying your content is a child of UINavigationController
, UITabBarController
, or a custom container view controller. You may find yourself needing to subclass a container view controller in order to modify the values returned by its supportedInterfaceOrientations
and shouldAutorotate
methods.
在Swift中,您可以override those methods by using extensions。
来自 iOS 开发者库 Technical Q&A
Autorotation is implemented by applying a rotation transform to the application's window when the system determines that the interface must rotate. The window then adjusts its bounds for the new orientation and propagates this change down through the view controller hierarchy via calls to each view controller's and presentation controller's -viewWillTransitionToSize:withTransitionCoordinator: method. Invocations of this method are provided a transition coordinator object containing the delta of the window's current transform and new transform, which can be retrieved by sending a -targetTransform message to the transition coordinator. Your view controller or presentation controller can derive the appropriate inverse transform and apply it to the target view. This nullifies the effect of the window transform on that particular view and gives the appearance that the view has remained fixed in its current orientation as the rest of the interface rotates normally.
即如果您有一个名为 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;
}
}
上制作一个演示项目
在多次与 Apple 来回交流之后,他们指向相同的 link 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")
}
}
这似乎在旋转子视图的同时保持主视图静止。此外,当通知进入时,该应用程序似乎知道正确的方向(因为母视图实际上在幕后旋转)。
特别感谢 jamesk 和 Warif Akhand Rishi 的所有帮助!
我想做什么:
由父视图控制器管理的父视图不应旋转。
由子视图控制器管理的子视图应该旋转到所有方向。
我试过的:
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)
}
ChildViewController
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .All
}
override func shouldAutorotate() -> Bool {
return true
}
Xcode 部署信息中支持的方向设置为全部四个。
我得到的:
None 视图的旋转。如果我将父级的旋转设置为全部,则所有视图一起旋转。所以要么全有要么全无。
更新
当我尝试为 UIDeviceOrientationDidChangeNotification 放置一个观察者并使用 UIDevice.currentDevice().orientation 使用 CGAffaineTransformMakeRotate 旋转 childView 时,我得到了想要的结果。但是,即,如果我旋转到横向,并尝试拉下通知中心(或者如果我收到系统通知),它仍然处于纵向方向(因为应用程序本身仍然留在纵向),旋转 childView 旋转回纵向以纪念状态 bar/notification/notification 居中。
Stock iOS Camera App 是我能举出的最好的例子。主要 canvas 不会旋转到不同的方向,但状态栏在幕后会旋转以响应设备旋转。子视图也会在它们自身内旋转以适应不同的方向。我正在努力实现这种行为....
在相机应用中实现的效果类似于使用以下组合:
- Technical Q&A QA1890 中描述的防止视图旋转的技术;和
- 使用子 UIStackViews 在设备旋转时重新排列子视图(参见 SimpleExampleViewController class in the AdaptiveElements sample code shown in WWDC 2016 Session 233)。
技术说明解释了自动旋转的底层实现涉及将转换直接应用于应用程序的 window:
Autorotation is implemented by applying a rotation transform to the application's window when the system determines that the interface must rotate. The window then adjusts its bounds for the new orientation and propagates this change down through the view controller hierarchy via calls to each view controller's and presentation controller's
viewWillTransitionToSize:withTransitionCoordinator:
method.
请注意,相机应用不会选择退出自动旋转:使用相机应用时,通知中心和控制中心的方向会反映设备方向。只有相机应用程序中的特定视图似乎选择退出自动旋转。事实上,this answer suggests that such apps typically utilise the videoGravity
and videoOrientation
properties provided by AVFoundation classes such as AVCaptureVideoPreviewLayer and AVCaptureConnection.
您应该采取的方法取决于您是只想阻止父视图旋转,还是想阻止容器视图控制器(例如 UINavigationController)旋转(即锁定设备方向)。
如果是前者,请使用技术说明中描述的技术来固定父视图的方向,并且(对于 iOS 9)将旋转子视图包装在 UIStackView 中并使用 axis
属性 在设备旋转时重新排列子视图,或(对于 iOS 8)在设备旋转时手动旋转子视图(参见
如果是后者,则说明您采用的方法是正确的。有关示例代码,请参阅 this answer。您需要共享您的代码和有关视图控制器层次结构的更多信息,以便我们诊断涉及通知中心的怪癖。
附录:Technical Q&A QA1688 中解释了对 shouldAutorotate
和 supportedInterfaceOrientations
的调用未传播到子视图控制器的原因:
Beginning with iOS 6, only the topmost view controller (alongside the
UIApplication
object) participates in deciding whether to rotate in response to a change of the device's orientation. More often than not, the view controller displaying your content is a child ofUINavigationController
,UITabBarController
, or a custom container view controller. You may find yourself needing to subclass a container view controller in order to modify the values returned by itssupportedInterfaceOrientations
andshouldAutorotate
methods.
在Swift中,您可以override those methods by using extensions。
来自 iOS 开发者库 Technical Q&A
Autorotation is implemented by applying a rotation transform to the application's window when the system determines that the interface must rotate. The window then adjusts its bounds for the new orientation and propagates this change down through the view controller hierarchy via calls to each view controller's and presentation controller's -viewWillTransitionToSize:withTransitionCoordinator: method. Invocations of this method are provided a transition coordinator object containing the delta of the window's current transform and new transform, which can be retrieved by sending a -targetTransform message to the transition coordinator. Your view controller or presentation controller can derive the appropriate inverse transform and apply it to the target view. This nullifies the effect of the window transform on that particular view and gives the appearance that the view has remained fixed in its current orientation as the rest of the interface rotates normally.
即如果您有一个名为 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;
}
}
上制作一个演示项目
在多次与 Apple 来回交流之后,他们指向相同的 link 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")
}
}
这似乎在旋转子视图的同时保持主视图静止。此外,当通知进入时,该应用程序似乎知道正确的方向(因为母视图实际上在幕后旋转)。
特别感谢 jamesk 和 Warif Akhand Rishi 的所有帮助!