iOS横屏的深入研究

iOS横屏模式分2种:跟随系统自动旋转、强制横屏。无论哪种横屏模式,都有2中实现途径:1.重写系统旋转方法。2.对view执行transform。
注:下文只讲解iOS6以后的横屏配置,ios6以前的版本太老旧,基本没有app支持了,所以省略。

一、跟随系统自动旋转
跟随系统自动旋转就是关闭竖屏锁定,让屏幕随重力感应旋转,该模式下笔者推荐通过重写系统旋转方法实现
1.1 app自动旋转的触发流程
我们先了解下app自动旋转的触发流程,然后才能更深入的理解如何实现旋转。

当手机的重力感应打开的时候, 如果用户旋转手机, 系统会抛发UIDeviceOrientationDidChangeNotification 事件,同时会读取plist文件获取app支持的旋转方向,如果此时在appDelegate中重写了

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window ,

那么会以重写的这个方法的返回值为准。然后会判断当前的controller是否为appDelegate的rootvc或者modal的vc,如果是则会读取该页面的以下三个属性:

- (BOOL)shouldAutorotate(是否支持自动旋转)、
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation(初始展示方向,只有modal模式下才会调用)、
- (UIInterfaceOrientationMask)supportedInterfaceOrientations(该页面支持的方向)

然后会根据preferredInterfaceOrientationForPresentation返回的结果展示初始视图。
这里需要注意一下的就是:如果shouldAutorotate返回值为YES的时候,infoplist文件中supportedInterfaceOrientations的值和你重写的该页面的supportedInterfaceOrientations返回值必须至少有一个交集,也就是这两个值与运算之后至少有一位为1来告诉系统旋转支持的方向,否则系统无法知道你到底想支持哪个方向旋转,会crash的(亲测)。如果shouldAutorotate返回值为NO时,前面两个supportedInterfaceOrientations可以没有交集,系统只会读取preferredInterfaceOrientationForPresentation的值去做初始化显示,但是保险起见,我们最好设置支持旋转方向的时候一定要保证有一个交集以上,避免苹果哪天检测严格出现不必要的crash。

1.2 自动旋转如何配置(采用重写系统旋转方法)
看了上文自动旋转的触发流程后,相信小伙伴们应该知道如何配置了,下面我还是给出详细步骤:

1.2.1 先配置app支持的旋转方向,可以有如下方式:


image.png

这种修改配置的方式其实根源就是修改infoplist文件
或者直接修改infoplist文件:


image.png

或者在appDelegate中重写:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    return UIInterfaceOrientationMaskAll;
}//优先级最高

1.2.2 指定横屏页面重写相关方法
如果需要横屏的页面是appDelegate的rootvc,或者是modal下的vc,直接在该页面重写以下方法就可以,否则系统不会主动调用(不过可以通过间接的形式调用,下文会讲)。

//是否支持自动旋转
- (BOOL)shouldAutorotate{
    return YES;
}
//初始的显示方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeRight;
}
//支持的旋转方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;//此处的返回值应该和infoplist文件中的值有交集,否则进入页面立马就会crash
}

如果需要旋转的页面是被push过来的(也就非rootvc或者非modal下的vc),我们可以在跟rootvc重写以上三个方法,然后方法内部的返回值全部由顶层的子vc决定,并且也在顶层子vc重写该方法。一般情况下我们的rootvc是tabbarvc,而且tabbarvc里面全部是navgationvc,navigationvc里面的某个顶层vc才是你需要横屏的页面,具体实现如下:
tabbarvc里重写:

- (BOOL)shouldAutorotate{
    return [self.selectedViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return [self.selectedViewController supportedInterfaceOrientations];
}

父类navgationvc里重写:

- (BOOL)shouldAutorotate{
    return [self.topViewController shouldAutorotate];
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return [self.topViewController supportedInterfaceOrientations];
}

需要横屏的vc里重写:

- (BOOL)shouldAutorotate{
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;//此处的返回值应该和infoplist文件中的值有交集,否则旋转手机就会触发调用该方法,然后crash
}

至此,自动横屏就配置完毕了,这里需要注意的是push进入的页面不会在加载该页面的时候就调用preferredInterfaceOrientationForPresentation方法来确定初始显示方向(因为这个初始显示方向的方法是present的vc才会被调用,push模式下rootvc也不会调用,这也就是我前面push的时候父类和子类都没有重写这个方法的原因),而是取的父navigationvc的显示方向,当屏幕旋转时根据你设备的方向和该页面支持的方向确定朝哪边旋转。也就是说我们没法通过push直接进入一个横屏页面,push的页面只有触发旋转才会进行横屏,想要一进入页面就展示横屏,我们还是只能以modal的形式进入!

另外,如果想通过对view执行transform实现自动旋转也可以,我们可以通过自主监听UIDeviceOrientationDidChangeNotification的方式实现。先监听UIDeviceOrientationDidChangeNotification,然后在监听的回调中获取设备方向,根据设备方向对view做相应的transform操作。不过这种方式相对繁琐,还有一些坑,不建议使用,在此不做具体展开,关于transform的代码下文强制横屏中会讲。

二、强制横屏
2.1 重写系统旋转方法
强制系统朝某一方向显示,只需要在该页面重写如下方法:

- (BOOL)shouldAutorotate{
    return NO;//关闭自动旋转
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationLandscapeRight;//初始化朝右边显示
}

这里有个坑需要说明下:
坑1:强制某一方向横屏只能再model模式下实现,push模式下不行。小伙伴可能会问了,我按照上文自动旋转的方式把父类也重写了也不行吗?答案是不行,前面已经讲过了,push进入到一个页面的时候,是不会触发任何旋转类的方法的,只有旋转手机才会调用。
坑2:网上有些资料写的通过runtime的,调用setOrientation的形式是不可行的,该方法仅使用ios6以前的设备!

//以下仅仅使用ios6以下的设备,小伙伴不要被误导!
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 = UIInterfaceOrientationLandscapeRight;
    [invocation setArgument:&val atIndex:2];
    [invocation invoke];
}

2.2 对view执行transform
该方法就是对当前的view执行一个90的旋转,不改变系统的显示方向。
viewDidLoad里实现如下方法:

//改变当前视图bounds的宽高
   self.view.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
//对当前视图做90度旋转
   [UIView animateWithDuration:0.2 animations:^{
       self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
   }];

viewWillAppear里:

//隐藏状态栏
[[UIApplication sharedApplication] setStatusBarHidden:YES];

viewWillDisappear里:

//恢复状态栏
[[UIApplication sharedApplication] setStatusBarHidden:NO];

这里也有个坑需要注意下:
当我们给view做transform的时候,系统的方向仍然还是竖屏,此时我们获取到的安全区域偏移量safeAreaInsets还是竖屏的,当我们需要针对iphoneX系列做布局的时候就有问题了。比如iphoneX竖屏下,我们通过self.view.safeAreaInsets获取的结果是(44,0,34,0),如果我们通过向右旋转之后,该方式获取的结果仍然还是这个值,但是横屏状态下,我们希望获取到的结果是(0,44,0,34),也就是偏移量全部向左旋转90度得到的结果,因此我们需要在通过这种方式旋转屏幕的时候,需要对偏移量做修正,我们可以在一个公共类中写一个获取安全区域的方法,实现代码如下:

/**
 获取屏幕的安全区域
 @param orientation 显示方向(是显示方向,非设备方向)
 */
+ (CGRect)getSafeAreaWithOrientation:(UIInterfaceOrientation)orientation{
    CGRect safeRect = kScreen_Bounds;
    UIEdgeInsets insets = UIEdgeInsetsZero;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
    //xcode baseSDK为11.0或者以上
    if (@available(iOS 11.0, *)) {
        insets = [UIApplication sharedApplication].keyWindow.safeAreaInsets;
    }
#endif
    if (orientation == UIInterfaceOrientationLandscapeLeft) {
        safeRect = CGRectMake(safeRect.origin.x, safeRect.origin.y, safeRect.size.height, safeRect.size.width);
        insets = UIEdgeInsetsMake(insets.left, insets.bottom, insets.right, insets.top);
    } else if (orientation == UIInterfaceOrientationLandscapeRight){
        safeRect = CGRectMake(safeRect.origin.x, safeRect.origin.y, safeRect.size.height, safeRect.size.width);
        insets = UIEdgeInsetsMake(insets.right, insets.top, insets.left, insets.bottom);
    } else if (orientation == UIInterfaceOrientationPortraitUpsideDown){
        insets = UIEdgeInsetsMake(insets.bottom, insets.right, insets.top, insets.left);
    }
    safeRect = UIEdgeInsetsInsetRect(safeRect, insets);
    return safeRect;
}

然后我们就可以根据上面返回的安全区域对view的自视图进行布局了。

三、iOS8横屏中的坑
上文我们介绍两种横屏实现的方式,在第一种通过重写系统旋转方法的方式中,存在一个iOS8的坑:iOS8系统的手机在横竖屏切换时,偶现window横竖屏的宽高不能正常变换问题。比如采用该方式实现横屏时,理论上竖屏变横屏的时候,竖屏的宽应该变成横屏的高,竖屏的高应该变成横屏的宽,但是iOS8的手机有时候就会出现无法变换的情况(查过一些资料,说这是苹果的一个bug)。
所以我们需要针对iOS8做一个横屏的补偿适配,思路是:在横屏的页面中,针对iOS8,我们可以先判断window的宽是否大于高,如果大于则是正常的不需要矫正,如果小于或者等于则对它的宽高做交换。
在viewWillAppear中实现如下代码:

//针对ios8横屏偶现的window宽高错乱问题做适配
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 9.0 && [[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
        UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
        CGRect windowFrame = keyWindow.frame;
        if (windowFrame.size.width < windowFrame.size.height) {
            windowFrame = CGRectMake(0, 0, windowFrame.size.height, windowFrame.size.width);
            keyWindow.frame = windowFrame;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容