UIDeviceOrientation
UIInterfaceOrientation
UIInterfaceOrientationMask
- (BOOL)shouldAutorotate;
- (UIInterfaceOrientationMask)supportedInterfaceOrientations;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
基础概念
UIDeviceOrientation
:设备朝向UIInterfaceOrientation
:页面内容朝向UIInterfaceOrientationMask
:页面支持的朝向
UIDeviceOrientation
UIDeviceOrientation
表示设备朝向,可以通过该方法获取:
|
|
UIDeviceOrientation
的取值有:
|
|
UIInterfaceOrientation
UIInterfaceOrientation
表示页面内容朝向。
⚠️注意:UIInterfaceOrientation
与 UIDeviceOrientation
的关系。不同枚举中的两个值可能相等:
|
|
这是因为向左旋转设备需要向右旋转内容。
UIInterfaceOrientation
的取值有:
|
|
可以通过下面👇这个方法获取当前状态栏朝向:
|
|
UIInterfaceOrientationMask
UIInterfaceOrientationMask
表示页面支持的朝向。
UIInterfaceOrientationMask
的取值有:
|
|
比如 UIInterfaceOrientationMaskLandscape
是由 MaskLandscapeLeft
和 MaskLandscapeRight
组成,是由页面内容朝向的二进制偏移组成,这样可以方便设备支持两个横屏方向。
横竖屏之UIViewController相关方法
- (BOOL)shouldAutorotate;
- (UIInterfaceOrientationMask)supportedInterfaceOrientations;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
shouldAutorotate
|
|
用来返回页面是否支持自动旋转,或者说是否跟随屏幕方向进行旋转。
默认值是
YES
,表示当前页面允许跟随设备旋转而自动旋转。iOS 16 相关改动:
[UIViewController should Autorotate]
已被弃用,不再受支持。[UIViewController attmptRotationToDeviceOrientation]
已被弃用,并替换为[UIViewController setNeedsUpdateOfSupportedInterfaceOrienttions]
。解决方法:依赖于
shouldAutorotate
的应用程序应使用支持的视图控制器InterfaceOrientations
反映其首选项。如果支持的方向更改,请使用-[UIViewController setNeedsUpdateOfSupportedInterface]
supportedInterfaceOrientations
|
|
用来返回当前页面支持的页面朝向,可以返回四个朝向的任意组合。这个方法返回的前提是 shouldAutorotate = YES
。
preferredInterfaceOrientationForPresentation
|
|
当页面被 present 出来的时候,返回该页面支持的朝向。可以返回四个朝向的任意组合。如果没有返回,则 present 时和原来页面的方向保持一致。
横竖屏之APPDelegate相关方法
- 方法一
在 XCode 的工程设置的 General
里,设置 iPhone 和 iPad 的页面朝向支持。
这种修改配置的方式其实就是修改 info.plist 文件,所以可以直接修改 info.plist 文件👇。
- 方法二
- 方法三
这个方法实现在 APPDelegate 里,根据需要返回当前 window 是否支持横屏,优先级最高:
|
|
该方法等效于 XCode 工程设置里的页面朝向支持,但是更灵活。
📢注意:以下内容都是基于设置了 UIInterfaceOrientationMaskAll
的情况下实现的,如果有其它情况会单独作补充。
自动旋转
关闭方向锁定,让屏幕随重力感应旋转。
自动旋转
指的是旋转设备时,系统会触发界面的旋转。页面设置shouldAutorotate = true
,当设备旋转方向后,会通过-supportedInterfaceOrientations
方法,获取页面支持的方向,并更改页面朝向。手动旋转
也可以说成主动旋转,通过调用修改设备方向的 api 来修改设备当前的朝向。如 UIViewController 的+attemptRotationToDeviceOrientation
,UIDievice 的-setOrientation:
方法。+attemptRotationToDeviceOrientation
是将界面朝向对齐设备朝向,是标准 api。-setOrientation:
是调整设备朝向,是私有 api。iOS 16 相关改动:
[UIViewController should Autorotate]
已被弃用,不再受支持。[UIViewController attmptRotationToDeviceOrientation]
已被弃用,并替换为[UIViewController setNeedsUpdateOfSupportedInterfaceOrienttions]
。解决方法:依赖于
shouldAutorotate
的应用程序应使用支持的视图控制器InterfaceOrientations
反映其首选项。如果支持的方向更改,请使用-[UIViewController setNeedsUpdateOfSupportedInterface]
App 自动旋转触发流程
当手机的重力感应打开的时候,旋转手机,系统会触发 UIDeviceOrientationDidChangeNotification 事件,同时读取 plist 文件中的支持朝向。
如果在 AppDelegate 中重写了下面这个方法👇,那么会以重写这个方法的返回值为准。
|
|
然后会判断当前的 ViewController 是否为 AppDelegate 的 rootvc 或者 modal 的 vc,如果是则会读取该页面的以下三个属性:
|
|
系统会根据 -preferredInterfaceOrientationForPresentation
方法返回的结果展示初始视图。
📢注意:在 - (BOOL)shouldAutorotate;
返回 YES
时,系统会调用该页面的 supportedInterfaceOrientations
方法获取页面朝向。该页面的 supportedInterfaceOrientations
返回值,必须是 plist 文件中 supportedInterfaceOrientations
包含的值。否则会 crash。
App 自动旋转实现
场景一
此处需要横屏的 ViewController 是 AppDelegate 的 rootVC,或者是 modal 下的 vc。
- 先配置app支持的旋转方向,可以在 XCode 工程设置、info.plist 文件和 AppDelegate 三中方法中的任意一种。
- 指定横屏页面重写相关方法;
|
|
场景二
此处需要横屏的 ViewController 是被 push 过来的(非rootvc和modal下的vc)。
- 先配置app支持的旋转方向,可以在 XCode 工程设置、info.plist 文件和 AppDelegate 三中方法中的任意一种。
- 指定横屏页面重写相关方法;
|
|
|
|
|
|
📢注意:通过 present 方式进入的页面会调用 preferredInterfaceOrientationForPresentation
方法,确定初始显示方向。通过 push 的方式进入的页面,不会再加载页面时调用 preferredInterfaceOrientationForPresentation
方法。无法实现通过 push 方式,直接进入一个横屏页面,只有触发旋转才会横屏展示,想要实现一进入页面就展示横屏,只能以 present(modal) 的形式进入。
场景三
除了重写系统方法外,还可以通过 transform 的方式实现自动旋转。
- 监听 UIDeviceOrientationDidChangeNotification 在监听回调中获取设备方向;
- 根据设备方向对 view 做相应的 transform 操作;
强制横屏
强制横屏的实现方案:
- 重写系统旋转方法;
- 视图适配:通过 transform 修改 layer,从而在视图上实现横屏,但是此时屏幕宽度、状态栏、安全距离等都保留竖屏状态,这种方式仅仅适用于横屏弹窗等部分场景。
竖屏页面 present 横屏页面
- 设置
secondViewController
的modalPresentationStyle
为UIModalPresentationFullScreen
; - 在
secondViewController.m
文件,实现preferredInterfaceOrientationForPresentation
方法,返回UIInterfaceOrientationLandscapeRight
;
|
|
|
|
补充:上面的代码有可能不生效,生效的前提是设置了 App 支持横屏方向,上面已经提到过,可以在 Xcode 里设置,也可以用代码设置👇
|
|
如果这个方法返回的是 [self.window.rootViewController supportedInterfaceOrientations]
,则上面的“竖屏 present 横屏”方案不生效。
|
|
解决方案:在通用工具的单例里添加“是否支持横屏”的属性,在需要 present 横屏前设置为 true,不 dismiss 前设置为 false,并在 AppDelegate.m 文件的方法里返回对应方向👇。
|
|
🤔思考:
- 如何实现横屏转竖屏?
- 如何实现自定义旋转效果?
- 横屏返回竖屏需要怎么处理?
📢注意:
- 强制某一方向横屏只能在 modal 模式下实现,push 模式下不行。
- 通过 runtime 调用 setOrientation 的形式是不可行的,该方法仅支持 iOS6 以下的系统:
|
|
竖屏页面 push 横屏页面
方法一
|
|
|
|
🤔思考:
- 这里为什么没有用到
UIViewController
的三个方法? - 在 viewDidLoad 方法内部调用的旋转方法是什么意思?
方法二
UINavigationController
内部实现相关方法,包括- (BOOL)shouldAutorotate;
和- (UIInterfaceOrientationMask)supportedInterfaceOrientations;
secondViewController
内部实现相关方法,包括- (BOOL)shouldAutorotate;
和- (UIInterfaceOrientationMask)supportedInterfaceOrientations;
secondViewController
主动旋转设备方向。
|
|
|
|
|
|
view 的 transform
该方法是将 view 进行一个90度的旋转,不改变系统的显示方向。
|
|
📢注意:在全面屏手机中,可以通过 self.view.safeAreaInsets
获取到安全区域。竖屏状态下获取安全区域是 (40, 0, 34, 0)
,横屏状态下获取安全区域是 (0, 44, 0, 34)
。因为通过 transform 方式旋转 view,系统方向还是竖屏状态,所以获取到的安全区域不对。
自定义根据指定方向获取安全区域的方法:
|
|
AFPlayer
关于 transform 的使用,ZFPlayer 中有相关应用。
AFPlayer 支持小屏竖屏、大屏横屏状态,分别针对iOS15和iOS16做了对应的横屏方案。不支持小屏横屏状态。
iOS15
小屏 -> 大屏
- 自定义
ZFLandscapeWindow
,设置更控制器ZFLandscapeViewController_iOS15
,控制器中自定义一个playerSuperview
(UIView
); - 全屏时,修改设备方向为横向;
- 设备方向改变时,触发
viewWillTransitionToSize:withTransitionCoordinator:
方法,将播放器添加到playerSuperview
上,设置 playerSuperview、播放器 的大小为横屏大小;
大屏 -> 小屏
- 修改设备方向为竖向;
- 设备方向改变时,触发
viewWillTransitionToSize:withTransitionCoordinator:
方法,将播放器添加到containerView
上,containerView
是开发者创建播放器时的容器view,设置播放器的大小。
iOS 16
小屏 -> 大屏
- 自定义
ZFLandscapeWindow
,设置更控制器ZFLandscapeViewController
; - 全屏时,修改设备方向为横向;
- 设备方向改变时,触发
viewWillTransitionToSize:withTransitionCoordinator:
方法,将播放器添加到ZFLandscapeWindow.view
上 - 旋转播放器为横向,设置播放器坐标为对应 Window 上的frame
- 调用
setNeedsUpdateOfSupportedInterfaceOrientations
重新设置内容方向,将播放器添加到ZFLandscapeViewController.view
上,设置播放器大小为横屏大小
大屏 -> 小屏
- 修改设备方向为竖向;
- 设备方向改变时,触发
viewWillTransitionToSize:withTransitionCoordinator:
方法,将播放器添加到ZFLandscapeWindow.view
上 - 旋转播放器为横向,设置播放器坐标为对应 Window 上的frame
- 将播放器添加到
containerView
上,containerView
是开发者创建播放器时的容器view,设置播放器的大小。
横竖屏切换机制分析
- 工程配置文件没有设置支持横屏,为什么可以 push 出横屏页面?
- 工程配置、
APPDelegate
和UIViewController
,在横竖屏切换过程的关系是什么? - 自动旋转和手动旋转有什么区别?
系统如何知道 APP 对页面朝向的支持
APP 启动前
在 APP 启动前进程还未加载,代码无法运行,系统无法通过AppDelegate
或者UIViewController
这种代码的方式获取横竖屏的配置。所以,在这种情况下,工程配置中的 plist 文件中的横竖屏配置,可以帮助系统识别应该以什么样的朝向启动 APP。
在 plist 文件中增加横屏的支持,优点是开屏能够支持横屏,这样界面展示更加顺滑。缺点是开屏支持了横屏,导致启动的时候是横屏,但是[UIScreen mainScreen]
是横屏的大小,很多业务代码在通过[UIScreen mainScreen]
方法去获取屏幕的宽高时,会取到错误的值。APP 运行时
当 APP 进程加载完成,此时系统可以通过运行时询问的方式,来动态获取不同时机的界面朝向。
此时 APPDelegate 控制的事 UIWindow 层面的朝向,UIViewController 控制的是 ViewController 层级的朝向。需要注意的是,当我们返回 UIViewController 的朝向时,还要考虑父容器的朝向。通常一个 App 的界面层级是 UIWindow → RootViewController(父) → ViewController(子)。只在 UIWindow 返回界面朝向也是允许的,如上面的竖屏页面 push 横屏页面👆。
朝向冲突
在每次界面切换的时候,系统都会回调新的界面朝向,最终结果取 UIWindow 朝向、RootViewController 朝向、ViewController 朝向三者的与值。
如果 UIWindow 返回竖屏,RootViewController 和 ViewController 返回横屏,横屏不会生效。同样的,如果 UIWindow 返回横屏,RootViewController 和 ViewController 返回竖屏,竖屏不会生效。
朝向优先级:UIWindow 朝向 > RootViewController 朝向 > ViewController 朝向。
在界面切换的过程中,如果没有返回朝向值或朝向值未确定,系统更倾向于保持当前朝向不变。
横竖屏切换通知
NSNotification通知
|
|
UIViewController回调
|
|
参考博文:
iOS横屏的深入研究
iOS横竖屏切换