一直看动漫,而每一季度的番剧版权都很分散,于是手机上装了Acfun、 BiliBili、爱奇艺、优酷、搜狐视频等App。
在锁定竖屏时,这几个App在视频播放时,初始情况都为竖屏,点击旋转按钮切换为横屏。
用的时间久了就发现大家在播放视频时做屏幕强制旋转的方式都有点不一样。可以划分为两派:Acfun 、 BiliBili、爱奇艺 和 搜狐视频 转动的是播放视频的 view.transform ;优酷 转动的是 UIDevice 的 Orientation。
假如竖屏下以小视频播放,同时视频下有其他视图。那么这两种转动方式在 UI 上的差异为:
- 转动 view.transform 仅是视频图层转动,其他图层不转动。
- 转动 UIDevice 其他图层也会转动。
如何让项目支持屏幕旋转,支持旋转的方向,如何仅让某一个控制器支持屏幕旋转,转动时执行的方法,参考下面两篇文章:
注意: 决定屏幕旋转的控制权。iOS6.0+ 之后,决定屏幕旋转的控制权的优先级为 application > window.rootViewController > currentViewController。
如果 viewController 是模态出来的,那么屏幕旋转的控制权还与模态方式有关。
方式1: UIDevice 的 orientation
改变 UIDevice 的 orientation,实际上强制改变了设备朝向,从而使 window,rootVC,currentVC 视图朝向全部发生了改变。
- 改变了 UIDevice 的 orientation
- 触发屏幕旋转方法 viewWillTransitionToSize(iOS8.0+)
- 触发 sizeClass 改变方法 willTransitionToTraitCollection
优点:
- 视图的旋转由系统自动实现
- 可以触发 横竖屏 sizeClass,所以适用 IB 中不同 sizeClass 下做的约束。
缺点:
改变的是 UIDevice 的方向,所以所有视图都发生了旋转。
实现
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
iOS6.0之后, 改为了私有方法,不能直接调用。
虽然不可以直接调用,但是我们可以用 KVC 或者 NSInvocation 方式调用。
1 | + (void)forceOrientation:(UIInterfaceOrientation)orientation { |
ps: 里脊串的文章中,提到使用这种方式上架无碍。
方式2: controller.view.transform
如果想要在多视图的情况下,仅使某一视图旋转,可以旋转 view.transform。
- 改变了 view.transfrom
- 不会触发屏幕旋转方法
- 不会触发 sizeClass 改变方法
优点:
- 仅一个视图发生旋转
缺点:
- 实现较为繁琐,需要自己实现视图旋转
- 不会触发 sizeClass 改变,所以需要用代码写两套约束。
实现
如果选择使用改变 view.transform 来模拟横竖屏旋转。那么要实现的功能如下:
- 点击旋转按钮后,根据当前设备朝向,view 转动 90° 或者 -90°,frame 大小为全屏。
- 在横屏状态 left 和 right 切换时,view 转动 180°
- 再次点击旋转按钮,根据当前设备朝向,view 转动 -90° 或者 90°,frame 大小为窗口。
下面放了一张手绘图,可以看的很明白。
要解决的问题有以下几个:
- 小屏切换到竖屏时如何获得当前当前设备方向。 要根据当前设备方向来决定转 90° 还是 -90°。
- 横屏两种状态下,如何自动旋转180°。
- 如何转动状态条。
下面逐个分析解决。
问题1: 竖屏切换横屏时如何判断当前设备方向
一提到获取设备当前方向,普遍会想到这有什么难的。直接使用 [UIDevice currentDevice].orientation 来获取当前设备朝向不就行了么?
1 | typedef NS_ENUM(NSInteger, UIDeviceOrientation) { |
如果仅是简单的获取具体的设备朝向,当前可以,我们会获取到某一个枚举值。 但是如果用户斜着拿手机呢?
UIDeviceOrientationUnknown
凡是不属于其他6种朝向的通划分为 unknown。 比如:斜着玩手机。
所以如果用户斜着拿手机,我们获取不到具体是 UIDeviceOrientationLandscapeLeft 还是 UIDeviceOrientationLandscapeRight。
如果想要获取到矢量方向,可以借助于加速计,使用加速计分析力的方向,区分横屏两个状态。
我们来看受力图中的第一张图,此时手机 y 轴旋转,home 在右边。那么重力G,沿着 x轴 和 z 轴被分解为 x力 和 z力。x力朝向为负, z力朝向为负。 此时设备方向应该划分为: UIDeviceOrientationLandscapeLeft。
第二张图,home 键在左边。重力G 被分解为 x力, z力。 x力为正,z力 为负。此时设备方向应该划分为:UIDeviceOrientationLandscapeRight。
按照力在 x 轴的受力方向,我们可以清楚的辩知,如果 x-,那么方向应为UIDeviceLeft ; 如果 x+ 方向应为UIDeviceRight。
1 | #pragma mark- 加速计获取x轴受力 |
点击旋转按钮时判断力的方向:
1 | - (void)liveViewRatation { |
问题2:横屏两种状态下,如何自动旋转180°
横屏下,随着设备转动 在 UIDeviceOrientationLandscapeLeft
和 UIDeviceOrientationLandscapeRight
方向下。我们需要转动 view,让 view.transfrom 转动 180°,视图朝向与设备朝向一致。
使用通知 UIDeviceOrientationDidChangeNotification 来观察设备方向。 代码如下。
1 | // 注册通知 |
问题3:如何转动状态条
[[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO]
该方法iOS6.0+ 之后也被弃用了,可以用 KVC 或者 objc_msgSend() 等。
注意: 如果发现状态条并没有旋转,把自动旋转设置为 NO
1 | - (BOOL)shouldAutorotate { |
旋转过程中发现,如果旋转开始时不隐藏状态条,会有视觉滞留。那么如何隐藏状态条呢?
[UIApplication sharedApplication].statusBarHidden = NO;
在 info.plist文件中,添加 View controller-based status bar appearance
项并设为 NO。
注意:如果使用 viewController 来隐藏或者显示状态条,则 View controller-based status bar appearance
设置为 YES。然后实现 prefersStatusBarHidden
方法。