之前有篇文章讲灵活的控制状态栏,这篇文章来讲一下控制屏幕旋转方向。
两篇文章的核心思想是一致的,都是将控制权传递给屏幕最上方的ViewController。
相信我,无论产品有什么坑爹需求,哪怕让你的App转出花来,在看了这篇文章之后,你的App想怎么转就怎么转!╰( ̄▽ ̄)╭
1. 配置App启动时的屏幕方向
如果你希望你的App以一个特定的方向启动,也就是LaunchScreen页面的旋转方向,那么就按照下图中的方式配置。
这是一个大家都熟悉的配置方式,但是要注意,这里只勾选一个方向。不要因为你的App中某些页面支持多个旋转方向,就在此处勾选多个。
在本文所讲的方法中,这里仅仅作为App启动时的屏幕旋转方向。
2. 配置App支持的屏幕旋转方向
不要担心第一步中只勾选一个方向,App就不支持别的旋转方向了,跟着第二步做就行了。
在AppDelegate里添加下面的方法:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// 这里建议return .all,即使你的App并不支持所有方向,具体的控制权在下面的步骤中
return .all
}
上面的方法是配置App支持的屏幕旋转方向,走到这个方法时,启动页(LaunchScreen)已经显示结束,开始进入App了。
这时你应该明白,为什么第一步的时候只勾选一个方向了。
3. 传递屏幕旋转控制权
屏幕旋转方向由下面几个方法控制:
extension UIViewController {
/**
这个方法是说屏幕是否要自动旋转,这里一般都return true。
该方法默认就是true,所以其实可以不复写该方法
*/
@available(iOS 6.0, *)
open var shouldAutorotate: Bool { get }
/** 支持的旋转方向。当前页面支持哪些旋转方向,在这个方法中return即可 */
@available(iOS 6.0, *)
open var supportedInterfaceOrientations: UIInterfaceOrientationMask { get }
/** 苹果对于该方法的注释大致是说:当前页面在某个方向下显示效果最佳,这个方向是首选的。ViewController.view将会以这个首选方向显示。
但实际上我发现并不是这样。比如当前页只支持横屏,我return .landscapeRight,它的上一页是竖屏。
那么进入该页面时按理说应该是横屏,但实际上仍然是竖屏,还需要手动旋转后才会横屏。
所以还需要下面第四个方法配合来实现进入当前页面时以首选方向显示。
*/
@available(iOS 6.0, *)
open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { get }
}
// 通过KVC来设置进入当前页面时的屏幕旋转方向
UIDevice.current.setValue(preferredInterfaceOrientationForPresentation.rawValue, forKey: "orientation")
介绍完控制方法,下面讲具体实现。本文以「TabBarController -> NavigationController -> ViewController」这样常见的结构为例。
TabBarController中增加如下代码:
// MARK: - Interface Orientation
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
guard let selectedVC = selectedViewController else { return .portrait }
return selectedVC.preferredInterfaceOrientationForPresentation
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
guard let selectedVC = selectedViewController else { return .portrait }
return selectedVC.supportedInterfaceOrientations
}
override var shouldAutorotate: Bool {
guard let selectedVC = selectedViewController else { return true }
return selectedVC.shouldAutorotate
}
NavigationController基类中增加如下代码:
// MARK: - Interface Orientation
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
guard let topVC = topViewController else { return .portrait }
return topVC.preferredInterfaceOrientationForPresentation
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
guard let topVC = topViewController else { return .portrait }
return topVC.supportedInterfaceOrientations
}
override var shouldAutorotate: Bool {
guard let topVC = topViewController else { return true }
return topVC.shouldAutorotate
}
这样,就一步步的将屏幕旋转的控制权传递给屏幕最上方的ViewController了,而在ViewController的基类中,也需要增加如下代码:
// MARK: - Interface Orientation
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
// 在此处配置默认的首选旋转方向
return .portrait
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
// 在此处配置默认支持的旋转方向
return .portrait
}
override var shouldAutorotate: Bool {
return true
}
// MARK: - Life Cycle
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 此处通过KVC设置进入当前页面时的旋转方向为preferredInterfaceOrientationForPresentation中设置的首选方向
UIDevice.current.setValue(preferredInterfaceOrientationForPresentation.rawValue, forKey: "orientation")
}
上面四个方法添加到UIViewController的基类中。当某个页面有特殊的旋转方向需求时,只需在那个ViewController中复写前两个方法即可。
严格来说,文章开头以及这一步骤标题说的“传递控制权”其实是不对的。
屏幕旋转方向的控制并不像状态栏(StatusBar)那样默认只由keyWindow.rootViewController
来控制,其实系统内部本来就会传递到屏幕最上方的ViewController中。
也就是说,其实可以不在TabBarController和NavigationController中添加那些代码,也是没有问题的。
但我觉得还是有必要的。首先是这样使得控制权的传递逻辑显得清晰;其次是如果有特殊情况时,方便在TabBarController和NavigationController中对整个App或者一个视图栈进行整体控制。
好啦~ 通过上面三个步骤,你的App就可以想怎么转就怎么转了!
然后,这里是本文中用到的Demo地址。使用时记得先把Scheme切换到ScreenRotationDemo上哦!