摘要:如何更灵活便捷的实现自定义屏幕旋转场景,本文带你揭秘!

文|即构 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)sizewithTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {//横屏:size.width > size.height//竖屏: size.width < size.heightNSLog(@"旋转完成,更新布局");}];
}

三、相关问题

在开发旋转场景的需求的时候,由于复杂的多级配置和数目繁多的枚举类型,难免会遇到一些崩溃和无法旋转的问题,下面我们就来总结一下此类问题。

问题一:无法自动旋转

首先检查下系统屏幕旋转开关是否被锁定。系统屏幕锁定开关打开后,应用内无法自动旋转,但是可以调用上文提到的的方法进行手动旋转。

问题二:多级屏幕旋转控制设置错误

以下方法都可以设置屏幕旋转的全局权限:

  • 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:selfselector:@selector(applicationWillResignActive:)name:UIApplicationWillResignActiveNotification object:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:@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:observerselector:@selector(onDeviceOrientationChange:)name:UIDeviceOrientationDidChangeNotificationobject: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 屏幕旋转的实践解析相关推荐

  1. iOS 屏幕旋转问题总结

    1.两个Orientation 1.1设备的物理方向(UIDeviceOrientation) typedef NS_ENUM(NSInteger, UIDeviceOrientation) {UID ...

  2. ios屏幕旋转学习笔记

    加速计是整个IOS屏幕旋转的基础,依赖加速计,设备才可以判断出当前的设备方向,IOS系统共定义了以下七种设备方向: typedef NS_ENUM(NSInteger, UIDeviceOrienta ...

  3. iOS屏幕旋转及其基本适配方法

    屏幕旋转示例.jpeg 前段时间抽空总结了一下iOS视频播放的基本用法,发现这其中还有一个我们无法绕过的问题,那就是播放界面的旋转与适配.的确,视频播放与游戏类型的App经常会遇到这个的问题.由于至今 ...

  4. iOS屏幕旋转 浅析

    一.两种orientation 了解屏幕旋转首先需要区分两种orientation 1.device orientation 设备的物理方向,由类型UIDeviceOrientation表示,当前设备 ...

  5. iOS屏幕旋转那些事

    前些日子处理了一些关于页面中的屏幕旋转的事情,需要根据某个页面的不同旋转方向去做不同的处理,现在再次总结一下,以在心里有个系统的体系,也可以当成工具文章查询. 首先来看第一个方法: [UIDevice ...

  6. IOS 屏幕旋转Orientation总结

    IOS Orientation, 想怎么转就怎么转~~~ 此博文主要针对IOS应用, 是屏幕旋转相关问题的一个总结. 主要内容有: IOS5,6,7不同版的适配. 强制旋转和自动旋转. 博客: htt ...

  7. iOS屏幕旋转技术点及解决方案总结

    1.屏幕旋转相关枚举 关于屏幕旋转枚举一共有3种: UIInterfaceOrientation,    UIInterfaceOrientationMask,   UIDeviceOrientati ...

  8. IOS屏幕旋转的检测 与 强行切换

    mark – 屏幕的手动切换 [[UIDevice currentDevice]setValue:[NSNumber numberWithInteger:UIDeviceOrientationLand ...

  9. iOS 屏幕旋转常用方式

    //此方法在进入应用和当屏幕旋转的额时候,会调用 - (UIInterfaceOrientationMask)application:(UIApplication *)application supp ...

  10. iOS屏幕旋转,强制横竖屏

    在 iPhone 应用里,有时我们想强行把显示模式从纵屏改为横屏(反之亦然),特别是需要展示音频,视频播放器的时候需要进行横竖屏切换,下面做下横竖屏总结,如有不全面之处欢迎留言补充. 目前横竖屏旋转方 ...

最新文章

  1. ASP.NET页面错误处理
  2. Exchange server 2003迁移到2010后,手动更新地址列表,提示OAB Versions无效
  3. git合并多个commit成为一个commit
  4. Android 设置view透明度,广告标题透明背景
  5. 人工智能在fpga的具体应用_人工智能带动了FPGA的发展
  6. vim删除多行_vim文本编辑器
  7. python 获取表格中的空数据_在python beautifulsoup中获取表格单元格值为空时的文本输出...
  8. cookie 操作详解 (asp.net javascript)
  9. HPE服务器做raid5阵列
  10. 基于JSP+Servlet的图书管理系统
  11. nmap 扫描服务器开放了哪些端口
  12. linux自动微信发信息,Linux下发送微信消息
  13. 抖音在线无水印解析PHP源码
  14. SQL注入中information_schema的作用
  15. php 回车符替换,php怎么替换回车符
  16. 51单片机按键控制数码管0~9_LED数码管精选电路方案合辑
  17. 修改加入SGE集群某队列里各个执行机的cpu数(slots)
  18. 杭州公司java开发工程师常见面试问题
  19. iOS 拓展,icon,尺寸 开发系列 吕文翰
  20. 基于Python的Covid-19全球疫情数据分析预测 文档+项目源码及数据

热门文章

  1. Android 点击按钮出现弹窗
  2. 小程序---小程序样式底部固定和顶部固定
  3. Modelica学习笔记2
  4. 怎么用python实现序列比对_生信学习笔记——Python+Mafft实现批量化多序列比对
  5. VS C#语言获取输入名称的汉语拼音简拼码和全拼码完整案例教程
  6. A8板卡AM3352移植环境搭建记录
  7. Keep in mind用法
  8. 剩余方差matlab,matlab 统计基本函数 var方差
  9. python 拼音地名对应关系,Python使用百度地图API根据地名获取相应经纬度
  10. 大数据和数据挖掘有什么关系