iOS 屏幕旋转的实践解析
摘要:如何更灵活便捷的实现自定义屏幕旋转场景,本文带你揭秘!
文|即构 iOS 应用开发团队
屏幕旋转是在视频直播类 APP 中常见的场景,在即构科技之前发布的 Roomkit SDK 中也有屏幕跟随手机自动旋转的场景。
在 Roomkit SDK 自身开发和客户接入的过程中我们也会发现,实现屏幕旋转的需求往往没有那么顺利,经常会出现无法旋转、旋转后布局适配等问题。
本篇文章根据我们以往的开发经验整理了屏幕旋转实现的相关实践方法,解析在实现过程中遇到的常见问题。
一、快速实现旋转
iOS 屏幕旋转的实现涉及到一堆枚举值和回调方法,对于没有做过旋转相关需求的开发来说,可能一上来就晕了,所以我们先动手,让屏幕转起来吧。
实现旋转的方式主要有两种,跟随手机感应旋转和手动旋转,接下来对这两种方式进行逐一介绍。
方式一:跟随手机感应器旋转
要实现自动跟随手机旋转,首先要让当前的视图控制器实现以下三个方法:
/// 是否自动旋转
- (BOOL)shouldAutorotate {
return YES;
}
/// 当前 VC支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}
/// 优先的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
这种方法需要注意以下几点:
shouldAutorotate 返回 YES 表示跟随系统旋转,但是受 supportedInterfaceOrientations 方法的返回值影响,只支持跟随手机传感器旋转到支持的方向。
preferredInterfaceOrientationForPresentation 需要返回 supportedInterfaceOrientations中支持的方向,不然会发生 'UIApplicationInvalidInterfaceOrientation'崩溃。
方式二:手动旋转
这种方式在很多视频软件中都很常见,点击按钮后旋转至横屏。
这时需要在 shouldAutorotate 中返回 yes,然后再在此方法中 UIInterfaceOrientation 传入你需要旋转到的方向。注意这是私有方法,是否使用请自行斟酌。
- (void)changeVCToOrientation:(UIInterfaceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
场景应用
自动旋转
如果你的 iPhone 没有关闭系统屏幕旋转,你就能发现系统相册 APP 的页面是可以跟着手机转动方向旋转的。
如果你想实现和它一样的效果,只需要按照前面方式一(跟随手机感应器旋转)去配置你的视图控制器的方法,之后控制器就可以在 supportedInterfaceOrientations 返回的方向内实现自由旋转了。
只能手动旋转
这种场景比较少见,在视频直播类 APP 中常见的场景是自动和手动旋转相结合的方式。
如果你要实现只能通过像点击按钮去旋转的方式,首先需要在 supportedInterfaceOrientations 方法中返回你需要支持的方向,这里重点是shouldAutorotate 方法的返回值。
上面方式二中(手动旋转)说明了手动旋转需要 shouldAutorotate 返回 YES,但是这也会让控制器支持自动旋转,不符合这个需求,所以我们按以下方法处理:
- (BOOL)shouldAutorotate {
if (self.isRotationNeeded) {
return YES;
} else {
return NO;
}
}
属性 isRotationNeeded 作为是否需要旋转的标记,isRotationNeeded 默认为 NO,此时就算你旋转设备,回调 shouldAutorotate 方法时也不会返回 YES,所以屏幕也不会自动旋转。
剩下的只需要你在点击旋转的按钮后将 isRotationNeeded 置为 YES 并调用手动旋转的方法,这样处理后只能手动旋转的效果就实现了。
二、旋转后的 UI 布局更新
通常情况下,应用旋转到横竖屏后,因为不同的宽高比会有不同 UI,所以在屏幕旋转的场景中我们又需要解决旋转后 UI 适配的问题。
手机旋转时,正常情况下若 shouldAutorotate 返回 YES , 当视图控制器需要旋转就会触发 viewWillTransitionToSize 方法,这样我们就找到了去更新横竖屏 UI 的时机了,也就是在 completion block 里去完成旋转后的适配逻辑。
/*
This method is called when the view controller's view's size is
changed by its parent (i.e. for the root view controller when its window rotates or is resized).
If you override this method, you should either call super to
propagate the change to children or manually forward the
change to children.
*/
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
//横屏:size.width > size.height
//竖屏: size.width < size.height
NSLog(@"旋转完成,更新布局");
}];
}
三、相关问题
在开发旋转场景的需求的时候,由于复杂的多级配置和数目繁多的枚举类型,难免会遇到一些崩溃和无法旋转的问题,下面我们就来总结一下此类问题。
问题一:无法自动旋转
首先检查下系统屏幕旋转开关是否被锁定。系统屏幕锁定开关打开后,应用内无法自动旋转,但是可以调用上文提到的的方法进行手动旋转。
问题二:多级屏幕旋转控制设置错误
以下方法都可以设置屏幕旋转的全局权限:
Device Orientation 属性配置:“TARGETS > General > Deployment Info > Device Orientation”,图中是 xcode 默认的配置,值得注意的是 iPhone 不支持旋转到 Upside Down 方向。
Appdelegate的 supportedInterfaceOrientationsForWindow 方法:
// 返回需要支持的方向
// 如果我们实现了Appdelegate的这一方法,那么我们的App的全局旋转设置将以这里的为准
- (UIInterfaceOrientationMask)application:(UIApplication *)applicatio supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window {
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskPortrait;
}
以上两种方式优先级:Appdelegate方法 > Target配置,这两种方式的配置和控制器的 supportedInterfaceOrientations 方法都会影响最终视图控制器最终支持的方向。
以 iOS 14 中以 present 打开控制器的方式为例,当前控制器最终支持的屏幕方向,取决于上面两种方式中的优先级最高的方式的值,与控制器 supportedInterfaceOrientations 的交集。
总结起来有以下几种情况:
如果交集为空,且在控制器的 shouldAutorotate 方法中返回为 YES,则会发生UIApplicationInvalidInterfaceOrientation 的崩溃。
如果交集为空,且在控制器的 shouldAutorotate 方法中返回为 NO,控制器的supportedInterfaceOrientations 方法与 preferredInterfaceOrientationForPresentation 方法返回值不冲突(前者返回值包含有后者返回值),则显示为控制器配置的方向。
如果交集为空,且在控制器的 shouldAutorotate 方法中返回为 NO,控制器的supportedInterfaceOrientations 方法与 preferredInterfaceOrientationForPresentation 方法返回值冲突(前者返回值未包含有后者返回值),则会发生 UIApplicationInvalidInterfaceOrientation 的崩溃。
如果交集不为空,控制器的 supportedInterfaceOrientations 方法与 preferredInterfaceOrientationForPresentation 方法返回值冲突,则会发生 UIApplicationInvalidInterfaceOrientation 的崩溃。
如果交集不为空,控制器的 supportedInterfaceOrientations 方法与 preferredInterfaceOrientationForPresentation 方法返回值不冲突,当前控制器则根据 shouldAutorotate 返回值决定是否在交集的方向内自动旋转。
这里建议如果没有全局配置的需求,就不要变更 Target 属性配置或实现 Appdelegate 方法,只需在要实现旋转效果的 ViewController 中按前面所说的方式去实现代码。
问题三:横屏时打开系统锁定屏幕开关,视图被强制恢复到竖屏
由于 iOS 闭源,苹果为什么会这样操作当然我们也无从得知,但是我们可以通过一些手段来规避这个问题。好在产生这样的旋转时,系统也会触发和普通旋转时一样的方法调用。
以 iPhone X 为例,当下拉打开控制页面时,我们会收到 UIApplicationWillResignActiveNotification 的系统通知,收起控制页面后会收到 UIApplicationDidBecomeActiveNotification 通知,通过这两个通知来记录一下状态,在 shouldAutorotate 通过判断是否是 Active 状态 返回 YES/NO。
- (void)setupNotification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (BOOL)shouldAutorotate {
if (!self.isApplicationActive) {
return NO;
} else {
return YES;
}
}
}
问题四:屏幕旋转与 ZegoExpressEngine 的适配
有很多小伙伴已经接入了我们的 ZegoExpressEngine 实时音视频引擎,那么在旋转的场景中你就要考虑到旋转对推拉流的影响,以 RoomKit SDK 的使用场景为例,大致有以下几种情况:
当前页面固定一个方向显示,只需要设置与当前方向符合的视频分辨率(引擎默认值为 “360 × 640”,根据自己需求确定),再调用引擎的 setAppOrientation 接口设置当前方向,以下代码以左横屏方向为例:
ZegoVideoConfig *videoConfig = [[ZegoVideoConfig alloc] init];
// 左横屏分辨率设置如下:
videoConfig.encodeResolution = CGSizeMake(1280, 720);
[[ZegoExpressEngine sharedEngine] setVideoConfig:videoConfig];
// 调用 setAppOrientation 接口设置视频的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:UIInterfaceOrientationLandscapeLeft];
当前页面有旋转的场景,这时就需要在旋转完成后去更新 ZegoExpressEngine 引擎的方向和视频分辨率,注意这里的当前方向取的是当前状态栏的方向。
// 根据当前方向设置分辨率
ZegoVideoConfig *videoConfig = [ZegoVideoConfig defaultConfig];
if (isCurPortrait) {
videoConfig.captureResolution = CGSizeMake(720, 1280);
} else {
videoConfig.captureResolution = CGSizeMake(1280, 720);
}
// 调用 setAppOrientation 接口设置视频的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:[UIApplication sharedApplication].statusBarOrientation];
上面的 ZegoExpressEngine 音视频引擎屏幕旋转后的适配逻辑,处理时机都在视图控制器旋转完成后,也就是 viewWillTransitionToSize 方法的 completion block 里面,这时拿到的 [UIApplication sharedApplication].statusBarOrientation 方向与当前控制器方向符合。
(更多 ZegoExpressEngine 音视频引擎屏幕旋转问题可以参考:iOS 实时音视频SDK视频旋转功能- 开发者中心 - ZEGO即构科技)
四、相关枚举值
在前面的讲述中,我们也认识了一些与屏幕旋转相关的枚举值。乍一看这块内容确实会感觉多得让人眼花缭乱,但是我们看清楚他们名称中的关键词如:Device、Interface,并在各个枚举类型用到的地方去理解它的意思,也是能理清这里面的逻辑的。
1、 设备方向:UIDeviceOrientation
UIDeviceOrientation 是以 home 键的位置作为参照,受传感器影响,和当前屏幕显示的方向无关,所以只能取值不能设值。
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);
前面讲述的屏幕旋转方法中不会直接用到这个枚举,但是如果你有监听设备当前方向的需求时,它就变得有用了。可以通过 [UIDevice currentDevice].orientation 获取当前设备的方向,若要监听设备的方向变化,可以用以下代码实现:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:@selector(onDeviceOrientationChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
2、 页面方向:UIInterfaceOrientation
UIInterfaceOrientation 是当前视图控制器的方向,区别于设备方向,它是屏幕正在显示的方向,preferredInterfaceOrientationForPresentation 方法的返回值就是这个枚举类型。
/// 优先的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
注意 UIInterfaceOrientationLandscapeLeft 与 UIDeviceOrientationLandscapeRight 是对应的,这两个枚举类型左右相反。
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);
3、 页面方向:UIInterfaceOrientationMask
观察 UIInterfaceOrientationMask 枚举的值,我们就会发现这是一种为了支持多种 UIInterfaceOrientation 而定义的类型,它用来作为 supportedInterfaceOrientations 方法的返回值,比如我们在该方法中返回 UIInterfaceOrientationMaskAll 就可以支持所有方向了。
/// 当前 VC支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} API_UNAVAILABLE(tvos);
五、结语
ZEGO RoomKit SDK 目前已经支持屏幕旋转场景,并且在 2.0.0 版本中以 JSON 配置的形式,支持更灵活更便捷的实现自定义的屏幕旋转场景。
在视频直播类的 APP 中屏幕旋转往往是绕不开的一环,梳理清楚以上三个枚举的含义,以及旋转方法的调用时机,并在恰当的时间去刷新旋转后的布局,iOS旋转适配就不再困难。
以上就是关于在 iOS 上实现屏幕旋转的技术解读,也欢迎大家使用 RoomKit SDK 体验 demo,点击链接,即可进行体验:开发者中心 - 即构科技
iOS 屏幕旋转的实践解析的更多相关文章
- IOS屏幕旋转思路和实践
这段时间同事在做一个直播项目,项目有个需求:一个界面需要手动设置屏幕的方向,设置好之后方向不能变化.完成这个需求花了特别大的精力,归因是网上关于屏幕旋转的知识比较凌乱,解决问题花费不少时间,最后决定把 ...
- iOS屏幕旋转 浅析
一.两种orientation 了解屏幕旋转首先需要区分两种orientation 1.device orientation 设备的物理方向,由类型UIDeviceOrientation表示,当前设备 ...
- 【转】IOS屏幕旋转与View的transform属性之间的关系,比较底层
iTouch,iPhone,iPad设置都是支持旋转的,如果我们的程序能够根据不同的方向做出不同的布局,体验会更好. 如何设置程序支持旋转呢,通常我们会在程序的info.plist中进行设置Suppo ...
- ios 屏幕旋转的问题
在ios6之前我们旋转屏幕只需要实现shouldAutorotateToInterfaceOrientation就行了 - (BOOL)shouldAutorotateToInterfaceOrien ...
- iOS 屏幕旋转 nav+tabbar+present(网页) 2016
如题,最近一个app架构为 nav + tabbar ,需求是 在点击tabbar中的一个菜单项时,弹出网页,该网页需要横屏显示,其他页面不变 都保持竖屏. XCode Version 7.2.1 ...
- iOS屏幕旋转
三种方法 需求:全局主要是竖屏 个别界面需要横屏
- 【转】IOS设备旋转的内部处理流程以及一些【优化建议】
加速计是整个IOS屏幕旋转的基础,依赖加速计,设备才可以判断出当前的设备方向,IOS系统共定义了以下七种设备方向: typedef NS_ENUM(NSInteger, UIDeviceOrienta ...
- ios实现屏幕旋转的方法
1.屏蔽AppDelegate下面的屏幕旋转方法 #pragma mark - 屏幕旋转的 //- (UIInterfaceOrientationMask)application:(UIApplica ...
- OpenGL ES 响应屏幕旋转 iOS
iOS下使用OpenGL 如果使用GLKit View 那么不用担心屏幕旋转的问题,说明如下: If you change the size, scale factor, or drawable pr ...
- OpenGL ES: iOS 自定义 UIView 响应屏幕旋转
iOS下使用OpenGL 如果使用GLKit View 那么不用担心屏幕旋转的问题,说明如下: If you change the size, scale factor, or drawable pr ...
随机推荐
- 【JavaScript】js中的浅拷贝与深拷贝与手写实现
前言 什么是深拷贝与浅拷贝?深拷贝与浅拷贝是js中处理对象或数据复制操作的两种方式.在聊深浅拷贝之前咱得了解一下js中的两种数据类型: 基本数据类型(6种) String.Number.Object ...
- Django导出EXCEL并确保表头左右两列显示
以下是导出EXCEL确保表头左右两列显示正确值的代码示例: from openpyxl import Workbook from openpyxl.styles import Alignment # ...
- Raid0创建
实验步骤 步骤1: 确认硬盘 确认你的硬盘设备名. [root@servera ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 ...
- Profinet转Modbus模块减轻通讯编程工作量实现Modbus通讯
巴图自动化PN转Modbus模块(BT-MDPN10)能够实现Profinet协议与Modbus协议之间的转换,使得Profinet协议设备与Modbus协议设备进行连接并能够相互通信. 通过使用巴图 ...
- C#:只支持GET和POST方法的浏览器,如何发送PUT/DELETE请求?RESTful WebAPI如何响应?
理想的RESTful WebAPI采用面向资源的架构,并使用请求的HTTP方法表示针对目标资源的操作类型.但是理想和现实是有距离的,虽然HTTP协议提供了一系列原生的HTTP方法,但是在具体的网络环境 ...
- JAVA Spring Boot快速开始
实践环境 Spring Boot 3.2.1 Maven 3.8.8 JDK 1.8.0_331 创建项目 通过http://start.spring.io/网站创建包含Spring Boot的项目, ...
- Java代码实现七夕魔方照片墙
创建一个七夕魔方照片墙是一个相对复杂的任务,涉及到前端展示和后端数据处理.在这里,我会提供一个简化的Java后端示例,用于生成一个模拟的"照片墙"数据模型,并给出一个基本的前端HT ...
- Jmeter函数助手28-urldecode
urldecode函数用于解码application/x-www-form-urlencoded字符串. String to encode in URL encoded chars:填入applica ...
- Netty的源码分析和业务场景
Netty 是一个高性能.异步事件驱动的网络应用框架,它基于 Java NIO 构建,广泛应用于互联网.大数据.游戏开发.通信行业等多个领域.以下是对 Netty 的源码分析.业务场景的详细介绍: 源 ...
- vue3 + ts 中出现 类型“typeof import(".........../node_modules/vue/dist/vue")”的参数不能赋给类型“Component<any, any, any, ComputedOptions, MethodOptions>”的参数。
错误示例截图 解决方法 修改shims-vue.d.ts中的内容 declare module "*.vue" { import { defineComponent } from ...