iOS 横竖屏切换解决方案
前言
在大多数项目中,App 的 UI 方向都是竖屏的,所以一般会在 target 中将屏幕方向设置为只支持竖屏。如下图
而在项目中可能又存在少量的横屏页面,或少数支持多方向的页面。视频播放页,通常都支持自动旋转。在我的项目中就出现这种情况,为了解决这个问题,所以对 UIViewController 进行了分类扩展。
方案使用
效果
默认情况下是竖屏转动手机,不跟随旋转,实现支持方向方法后,可以跟随。
Demo链接地址: 前往
引入
使用 Cocoapods
pod 'AutoRotation'
手动
前往项目地址,下载之后将 AutoRotation
文件夹中的文件拖到项目中即可
配置
将工程设置为只支持竖屏,如图
使用
由于项目中绝大多数页面都是竖屏的,所以默认情况下,所有的 Controller 将是竖屏展示。在需要横屏或多方向的 Controller 中实现代码如下:
#import
// 或
#import "AutoRotation.h"
// ViewController.m 中实现下面方法
- (UIInterfaceOrientationMask)ar_supportedOrientations {
// 支持三个方法
return UIInterfaceOrientationMaskAllButUpsideDown;
}
通过实现上述方法,即可让特定的 Controller 支持设定的方向。
其它 API
/// 转到指定方向
- (void)ar_turnToOrientation:(UIInterfaceOrientationMask)orientation;
/// 转为竖屏
- (void)ar_turnToPortrait;
/// 转为横屏
- (void)ar_turnToLandscape;
/// 添加特殊处理Controller,用于处理如 AlertContoller 等情况
+ (void)ar_addSpecialControllers:(NSArray *)controllers;
/// 移除特殊处理Controller
+ (void)ar_removeSpecialControllers:(NSArray *)controllers;
解决思路
- 使用 Runtime 交互 Appdelegate 和 UIViewController 的相关方法
- 获取当前可见的顶部 Controller 的方法
- 将顶部 Controller 支持的方向返回给 Appdelegate 中的
application:supportedInterfaceOrientationsForWindow:
方法 - 显示出对应用的方向
实现思路其实比较简单,当然中途会有一些问题,代码实现中对一些异常情况进行了处理。
屏幕方向枚举
iOS 中关于方向的枚举有三种
UIInterfaceOrientationMask
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),
} __TVOS_PROHIBITED;
UIInterfaceOrientation
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
UIDeviceOrientation
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
UIDeviceOrientationFaceUp, // Device oriented flat, face up
UIDeviceOrientationFaceDown // Device oriented flat, face down
} __TVOS_PROHIBITED;
手动更改屏幕方向
由于方案中并不更改工程设置,只勾选了竖屏,所以在实现中使用了手动转屏方法。代码如下:
+ (BOOL)setOrientation:(UIInterfaceOrientationMask)orientation {
UIInterfaceOrientation interfaceOrientation = [self getOrientationWithOrientationMask:orientation];
UIInterfaceOrientation currentOrientation = [UIViewController currentOrientation];
if (currentOrientation == interfaceOrientation) {
return NO;
}
NSLog(@"ar log: interfaceOrientation:%ld", interfaceOrientation);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[UIApplication sharedApplication] setStatusBarOrientation:interfaceOrientation animated:NO];
#pragma clang diagnostic pop
[[UIDevice currentDevice] setValue:@(interfaceOrientation) forKey:@"orientation"];
return YES;
}
实现
通过上述的基础及实现思路铺垫,总体实现起来相对简单。下面是碰到的一些问题。
topViewController 不是在 keyWindow 上
解决办法是获取 Appdelegate 中的 window 的 topViewController
AlertView 和 AlertController
解决办法是 hook dismissViewControllerAnimated
方法并且将相关的类名加入到特殊 Controller 列表中,在处理这些特殊 Controller 时,将获取前一个控制器的方向
具体实现代码可前往 Git 上查看