iOS横竖屏切换

基础概念

UIDeviceOrientation

UIDeviceOrientation,表示设备朝向,可以通过[UIDevice currentDevice] orientation]获取,取值有:

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,             // 未知,启动时会出现
    UIDeviceOrientationPortrait,            // 竖屏,home键在底部
    UIDeviceOrientationPortraitUpsideDown,  // 倒立,home键在顶部
    UIDeviceOrientationLandscapeLeft,       // 左横屏,home键在右边
    UIDeviceOrientationLandscapeRight,      // 右横屏,home键在左边
    UIDeviceOrientationFaceUp,              // 屏幕朝上
    UIDeviceOrientationFaceDown             // 屏幕朝下
}

UIInterfaceOrientation

UIInterfaceOrientation,表示页面内容朝向,注意UIInterfaceOrientation和UIDeviceOrientation的关系,其中UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,这是因为:
This is because rotating the device to the left requires rotating the content to the right.
不用特别细究两者之间关系,我们只需要根据需要设置好UIInterfaceOrientation即可,通过
[UIApplication shareApplication] statusBarOrientation]可以获取当前状态栏朝向。

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
};

UIInterfaceOrientationMask

UIInterfaceOrientationMask,是由页面内容朝向的二进制偏移组成,用来更方便描述某个界面支持的朝向。比如说下面的UIInterfaceOrientationMaskLandscape,其实就是由MaskLandscapeLeft和MaskLandscapeRight组成,这样可以方便描述设备支持两个横屏方向。

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),
};

UIViewController相关

UIViewController关于横竖屏的三个方法:

  1. shouldAutorotate,页面是否允许自动旋转,被弃用api:-shouldAutorotateToInterfaceOrientation的取代者;默认值为YES,表示当前界面允许跟随设备旋转而自动旋转;
  2. supportedInterfaceOrientations,该界面支持的界面朝向,可以返回四个朝向的任意组合,iPad默认值是四个朝向都支持,iPhone默认值是除了UpsideDown的三个朝向。这个方法回调的前提是shouldAutorotate=YES。
  3. preferredInterfaceOrientationForPresentation,该界面被present出来的界面朝向,可以返回四个朝向的任意组合。如果没有返回,则present时和原来界面保持一致。

AppDelegate相关

AppDelegate的supportedInterfaceOrientationsForWindow方法,根据需要返回当前window是否支持横屏。

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

工程配置相关

在xcode的工程设置的General可以配置iPhone和iPad的页面朝向支持。

横竖屏切换实例

竖屏界面如何present横屏界面

竖屏present横屏是很普遍的场景,比如说视频播放场景的全屏切换,就可以在当前竖屏的界面present一个横屏播放界面的方式,实现横竖屏切换。具体的操作步骤只需要两步:

1,设置modalPresentationStyle为UIModalPresentationFullScreen;
2、preferredInterfaceOrientationForPresentation方法,返回UIInterfaceOrientationLandscapeRight;

比如说下面的LandscapeViewController界面:

// 点击时设置
- (void)onClick {
    LandscapeViewController *landVC = [[LandscapeViewController alloc] init];
    landVC.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:landVC animated:YES completion:nil];
}

// LandscapeViewController内部代码
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

思考🤔:

1、如果是横屏转竖屏呢?
2、如果想要自定义旋转效果实现呢?(UIViewControllerAnimatedTransitioning协议)

竖屏界面如何push横屏界面

比如说这样的场景:App的rootVC是navigationVC,导航栈内先有一个竖屏界面,现在想要push一个横屏界面LandscapeViewController。

一个简单的方式如下:

// appdelegate实现
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if ([self.navigationVC.topViewController isKindOfClass:LandscapeViewController.class]) {
        return UIInterfaceOrientationMaskLandscapeRight;
    }
    else {
        return UIInterfaceOrientationMaskPortrait;
    }
}
// LandscapeViewController内部实现
- (void)viewDidLoad {
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:@selector(setOrientation:)]];
    invocation.selector = NSSelectorFromString(@"setOrientation:");
    invocation.target = [UIDevice currentDevice];
    int initOrientation = UIDeviceOrientationLandscapeRight;
    [invocation setArgument:&initOrientation atIndex:2];
    [invocation invoke];
}

思考🤔:

1、这里为什么没有用到UIViewController的三个方法?
2、在viewDidLoad调用的旋转方法是什么意思?

横屏竖切换机制分析

前面的实例介绍了如何支持切换,但是也产生一些疑问:
工程配置文件也没有设置横屏,为什么后面就能支持横屏?
工程配置、AppDelegate、UIViewController这三者,在横竖屏切换过程的关系是什么?
自动旋转和手动旋转有什么区别?
....
仅仅知道切换适配代码,是无法形成横竖屏切换理解,也就很难回答上述的问题。
由于没有找到解释横竖屏切换机制的官方文档,以下根据自己的经验对这个切换的机制进行分析。

系统如何知道App对界面朝向的支持

这里分两种情况,App启动前和App运行时。

App启动前
在App启动前进程还未加载,代码无法运行,系统肯定无法通过AppDelegate或者UIViewController这种代码的方式获取横竖屏的配置。所以在这种情况下,工程配置中的plist描述App对屏幕的适配,就可以很好帮助系统识别应该以什么样的朝向启动App。
所以在plist中增加横屏的支持,好处是开屏能够支持横屏,这样界面展示更加顺滑;坏处也是开屏支持了横屏,导致开屏为横屏启动的时候,UIScreen的mainScreen是横屏的大小,但很多业务逻辑代码都会以[UIScreen mainScreen]去取屏幕宽度和高度,所以很容易取到错误的值。

App运行时
当App进程加载完成,此时系统可以通过运行时询问的方式,来动态获取不同时机的界面朝向。
此时AppDelegate控制的是UIWindow层面的朝向,UIViewController控制的是VC层面的朝向。需要注意的是,当我们返回UIViewController的朝向时,还要考虑父容器的朝向。通常一个App的界面层级是UIWindow=>RootViewController(容器vc)=>UIViewController(界面vc)。假如只在UIWindow返回界面朝向也是允许的,就如同上面的实例分析中的push横屏。

不同界面的朝向控制

还是假设UIWindow=>RootViewController(容器vc)=>UIViewController(界面vc)的层级,且当前ViewController是竖屏vc,现在需要push一个横屏界面LandscapeViewController。
在每次界面切换的时候,系统都会回调确认新的界面朝向,最终结果为UIWindow朝向、容器vc朝向、界面vc朝向三者的“与”值。那么假如这个值冲突了呢?
假如supportedInterfaceOrientationsForWindow一直返回的竖屏,那么后面VC设置横屏不会生效;
类似,假如UIWindow设置的是横屏,那么后面VC设置竖屏也不会生效;
如果在界面切换的过程中发现返回的朝向值未确定,系统更倾向于保持当前朝向不变,并且可能会遇到以下的crash。

Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and [LandscapeViewController shouldAutorotate] is returning YES'

这个原则同样适用于当返回多个结果,比如说当前界面是竖屏,然后UIWindow和ViewController的界面朝向都支持横屏和竖屏都支持,那么此时会保持竖屏。

一种比较常用的设计:

// AppDelegate
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    return self.navigationVC.topViewController.supportedInterfaceOrientations;
}

// NavigationController
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return self.topViewController.supportedInterfaceOrientations;
}

// LandscapeViewController
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

自动旋转和手动旋转

自动旋转指的是我们旋转物理设备时,系统会触发界面的旋转。当我们从一个竖屏界面push一个横屏界面时,即使横屏界面设置了shouldAutorotate=YES,这个界面也不会变成横屏,但是拿起来设备左右翻转的时候,会发现随着设备旋转,界面也从横屏变成了竖屏。这就是自动旋转。
手动旋转指的是手动触发旋转,根据经验发现有两个api,UIViewController的+attemptRotationToDeviceOrientation,还有UIDevice的setOrientation:可以调整界面朝向。前者是将界面朝向对齐设备朝向,是标准api;后者是调整设备朝向,是私有api。
假如我们在很多个竖屏界面中,需要强制横屏某一个界面,如果是子界面可以使用present的方式,如果是push那么就必须要用到这个私有api。

注意事项

其他横竖屏适配方式

1、视图适配:通过transform修改layer从而在视图上实现横屏,但是此时屏幕宽度、状态栏、安全距离等都保留竖屏状态,这种方式仅仅适用于横屏弹窗等部分场景;
2、新建Window:由于App的适配是UIWindow为单位,那么理论上是可以新建一个UIWindow来横屏的界面;

横竖屏切换通知

NSNotification通知

[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"NSNotification:%@, orientation:%d", note.userInfo, [(UIDevice *)note.object orientation]);
    }];

UIViewController回调

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

推荐阅读更多精彩内容