加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

iOS屏幕旋转适配的基本方法

(2022-07-20 18:06:09)
标签:

ios

旋转

分类: 知识总结
iOS屏幕旋转适配的基本方法

 主要内容:
1. 最让人纠结的三种枚举
2.两种屏幕旋转的触发方式
3.屏幕旋转控制的优先级
4. 开启屏幕旋转的全局权限
5. 开启屏幕旋转的局部权限(视图控制器)
6. 实现需求:项目主要界面竖屏,部分界面横屏
7. 默认横屏无效的问题
8. 关于旋转后的适配问题
9. APP 启动即全屏

一、最让人纠结的三种枚举
刚开始接触屏幕旋转这块知识的时候,最让人抓狂的也许就是三种相关的枚举类型了,它们就是:
1.UIDeviceOrientation
2.UIInterfaceOrientation
3.UIInterfaceOrientationMask

1.设备方向UIDeviceOrientation
UIDeviceOrientation:
1. 这是硬件设备(iPhone、ipad 等)本身的当前旋转方向,共有7种(包括一种未知的情况);
2.设备方向只能取值,不能设置;
3. 判断设备的方向是以Home 键的位置作为参照的;
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
} API_UNAVAILABLE(tvos);

获取设备当前设备的旋转方向使用:[UIDevice currentDevice].orientation ;
为了监测设备方向的变化,可以在 Appdelegate 文件中使用通知如下:

[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(onDeviceOrientationDidChange)
                                                 name:UIDeviceOrientationDidChangeNotification
                                               object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

- (BOOL)onDeviceOrientationDidChange{
//获取当前设备Device
UIDevice *device = [UIDevice currentDevice] ;
//识别当前设备的旋转向
switch (device.orientation) {
case UIDeviceOrientationFaceUp:
NSLog(@"屏幕幕朝上平躺");
break;
case UIDeviceOrientationFaceDown:
NSLog(@"屏幕朝下平躺");
break;
case UIDeviceOrientationUnknown:
//系统当前法识别设备朝向,可能是倾斜
NSLog(@"未知向");
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(@"屏幕向左橫置");
break;
case UIDeviceOrientationLandscapeRight:
NSLog(@"屏幕向右橫置");
break;
case UIDeviceOrientationPortrait:
NSLog(@"屏幕直");
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(@"屏幕直,上下顛倒");
break;
default:
NSLog(@"無法识别");
break; }
return YES; 
}

2.向UIInterfaceOrientation
UIInterfaceorientation 是开发的程序界面的当前旋转方向,它是可以设置的;
UIInterfaceorientation 的源码定义如下:
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);

值得注意的两个枚举:
 UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

我们可以发现 设备向 与 向 的枚举值多是可以对应上的。只有左右旋转的时候
是 UIInterfaceOrientationLandscapeLeft 与 UIDeviceOrientationLandscapeRight 相等,反之亦然,这是因为向左旋转设备需要旋转程序界右边的内容。
3.向UIInterfaceOrientationMask
UIInterfaceOrientationMask 是 iOS6 之后增加的种枚举,其源码如下:

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

我们已经知道 UIDeviceOrientation 与 UIInterfaceOrientation 的区别在于:前者是真实的设备
向,后者是向;
UIInterfaceOrientation 和 UIInterfaceOrientationMask 的区别是什么呢?其实观察源码,我
们就会发现,这是种为了持多种UIInterfaceOrientation定义的类型;
下的示例将很好的说明这点:
在 iOS6 之后,控制单个界的旋转我们通常是下三个法来控制:
//法1 - (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
//法2 - (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED
// Returns interface orientation masks.
//法3 - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS
法2:于设置当前界持的所有向,所以返回值是 UIInterfaceOrientationMask ,更加
便的表达持多向旋转的情况;
法3:于设置进界默认持的向,使了返回值类型 UIInterfaceOrientation ,默认
进界的向是个确定的向,所以使 UIInterfaceOrientation 更适合;
二、两种屏幕旋转的触发方式
我们开发的 App 的,多情况都是多界持竖屏,个特别的界持旋转横屏,两种
界相互切换,触发其旋转有两种情况:
情况1:系统没有关闭动旋转屏幕功能,
这种情况,持旋转的界跟随户持设备旋转向动旋转。我们需要在当前视图控制
器中添加如下法:
//1.决定当前界是否开启动转屏,如果返回NO,后两个法也不会被调,只是会持默认的向
- (BOOL)shouldAutorotate {
return YES; }
//2.返回持的旋转向
//iPad设备上,默认返回值UIInterfaceOrientationMaskAllButUpSideDwon
//iPad设备上,默认返回值是UIInterfaceOrientationMaskAll
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll; }
//3.返回进界默认显示向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait; 
}
情况2:单个界强制旋转
在程序界,通过点击等式切换到横屏(尤其是视频播放的情况),有以下两种法:
// 方法1:
-(void)setInterface0rientation: (UIDeviceOrientation)orientation{
if ([[VIDevice currentDevice] respondsToSelector:@selector (setOrientation:)])
[[VIDevice currentDevice] setValue: [NSNumber numberWithInteger:orientation]
forKey: @"orientation"]:
}
}
// 法2:
- (void)interfaceOrientation:(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]];
        UIInterfaceOrientation val = orientation;
        [invocation setArgument:&val atIndex:2];
        [invocation invoke];
    }
}
注意:使这两个法的时候,也要确保 shouldAutorotate 法返回 YES ,这样这两个法才
会效。还要注意两者使的参数类型不同;
三、屏幕旋转控制的优先级
事实上,如果我们只上的法来控制旋转的开启与关闭,并不能符合我们的需求,且
法效。这是因为我们忽略了旋转权限优先级的问题;
屏幕旋转的设置有 3 个地:
1. Xcode 的 General 设置;
2. info.plist 的设置;
3. 通过代码设置;
这么多的设置很是繁杂,但是这些其实都是在不同级别上实现旋转的设置,我们会遇到设置
后效的情况,这就很可能是被上级别控制的原因;
这先有个致的了解,控制屏幕旋转优先级为:程Target属性配置(全局权限) =
Appdelegate&&Window > 根视图控制器> 普通视图控制器。
四、开启屏幕旋转的全局权限
这我使 全局权限 来描述这个问题可能不太准确,其实是设置我们的设备能够持的向有
哪些,这也是实现旋转的前提;
开启屏幕旋转的全局权限有三种法,包括通过 Xcode 直接配置的两种法和代码控制的种
法。
这三种法作相同,但是由于代码的控制在程序启动之后,所以也是最有效的。下分别
对三种法的法介绍:
1.Device Orientation属性配置
我们创建了新程, Xcode 就默认替我们选择了持旋转的个向,这就是 Device
Orientation 属性的默认配置。在 Xcode 中依次打开:【General】—>【Deployment Info】— >【Device Orientation】,我们可以看到默认持的设备向如下:
Device Orientation
可以发现, UpsideDown 没有被默认持,因为对于 iPhone 即使勾选也没有 UpSideDown 的旋转
效果。我们可以在这勾选或者取消以修改持的旋转向。如果是 iPad 设备勾选之后会同
时持四个向;
特殊情况:对于iPhone,如果四个属性我们都选或者都不选,效果和默认的情况样。

2.Info.Plist设置
其实我们设置了 Device Orientation 之后,再到 info.plist 中查看 Supported interface orientation ,我们会看到:
Supported interface orientations
没错,此时 Supported interface orientation 的设置和 UIDevice Orientation 的值致的,并
且我们在这增加或者删除其中的值, UIDevice Orientation 的值也会随之变化,两者属于同
种设置;
3.Appdelegate&&Window中设置
正常情况下,我们的 App 从 Appdelegate 中启动, Appdelegate 所持有唯的 Window 对象是全
局的,所以在 Appdelegate 件中设置屏幕旋转也是全局有效的。
下的代码设置了只持竖屏和右旋转:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window  API_AVAILABLE(ios(6.0)) API_UNAVAILABLE(tvos);

需要注意:如果我们实现了Appdelegate的这法,那么我们的App的全局旋转设置将以
这的为准,即使前两种法的设置与这的不同。
五、开启屏幕旋转的局部权限(视图控制器)
在设置了全局所持的旋转向后,接着就开始设置具体的控制器界了。我们在上已经
说明了关于旋转的优先级了。这主要涉及了三种视图控制器:
1. UITabbarViewController
2. UINavigationBarController
3. UIViewController
全局权限开启之后,接下来具有最权限的就是 Window 的根视图控制器 rootViewController
了。如果我们要具体控制单个界 UIViewController 的旋转就必须先看下根视图控制器的配
置情况了;
当然,在般情况下,我们的项都是 UITabbarViewController 作为 Window 的根视图控制
器,然后管理着若个导航控制器 UINavigationBarController ,再由导航栏控制器去管理普通
的视图控制器 UIViewController ;
若以此为例的话,关于旋转的优先级从到低就是
UITabbarViewController>UINavigationBarController >UIViewController了。如果具有优
先级的控制器关闭了旋转设置,那么低优先级的控制器是法做到旋转的;
如说我们设置要单个视图控制器可以动旋转,这需要在视图控制器中增
加 shouldAutorotate 法返回 YES 或者 NO 来控制。但如果存在上层根视图控制器,我们只在
这个视图控制器中实现法,会发现这个法是不的,因为这个法被上层根视图控制器
拦截了;
理解这个原理后,我们有两种法实现动可控的旋转设置:
法1:逐级设置各视图控制器,优先级的视图控制器影响低优先级控制器,
解决上述的问题我们需要设置 UITabbarViewController 如下:
//是否动旋转
-(BOOL)shouldAutorotate{
return self.selectedViewController.shouldAutorotate; }
//持哪些屏幕向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.selectedViewController supportedInterfaceOrientations]; }
//默认向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.selectedViewController preferredInterfaceOrientationForPresentation]; }
设置导航控制器 UINavigationController 如下:
//是否动旋转
//返回导航控制器的顶层视图控制器的动旋转属性,因为导航控制器是以栈的原因叠加VC的
//topViewController是其最顶层的视图控制器,
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate; }
//持哪些屏幕向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations]; }
//默认向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.topViewController preferredInterfaceOrientationForPresentation]; }
 
到这,我们就应该明了,其实就是优先级的视图控制器要跟随低优先级控制器的旋转
配置。这样就能够达到的;
法2: 另辟蹊径,使模态视图
使模态视图可以不受这种根视图控制器优先级的限制。这个也很容易理解,模态弹出的视
图控制器是隔离出来的,不受根视图控制的影响。具体的设置和普通视图器代码相同,这
就不累述了;
六、实现需求:项主要界竖屏,部分界横屏
这其实也是个我们做屏幕旋转最常的需求,在根据上的讲述之后,我们实现这个需求
会很容易,但是具体的实现却有着不同的思路,我在这总结了两种法:
法1:使基类控制器逐级控制
具体步骤:
1. 开启全局权限设置项持的旋转向;
2. 根据第五节中的法1,定义标签控制器和导航控制器来设置屏幕的动旋转;
3. 定义基类控制器设置不持动转屏,并默认只持竖屏;
4. 对项中需要转屏幕的控制器开启动转屏、设置持的旋转向并设置默认向;
Demo1 链接: https://github.com/DreamcoffeeZS/Demo_TestRotatesOne.git
法2:Appdelegate增设旋转属性
具体步骤:
1. 在 Applegate 件中增加个于记录当前屏幕是否横屏的属性;
2. 需要横屏的界,进界后强制横屏,离开界时恢复竖屏;
Demo2 链接: https://github.com/DreamcoffeeZS/Demo_TestRotatesTwo.git
七、默认横屏效的问题
在上的项中,我们可能会遇到个关于默认横屏的问题,把它拿出来细说下。
我们项中有持竖屏的 界A ,也有持横竖屏的 界B ,且 界B 需要进时就显示横
屏。从 界A 到 界B 中,如果我们使第五节中的法1会遇到法显示默认横屏的情况,因
为没有旋转设备, shouldAutorotate 就没被调,也就没法显示我们需要的横屏。这有两个
解决法:
法1:在定义导航控制器中增加以下法
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations]; }
//默认向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.topViewController preferredInterfaceOrientationForPresentation]; } 9
 
这个法的缺点是,原理上利弹出模态视图来调转屏,造成切换界的时候有闪烁效
果,体验不佳。所以这也只是提供种思路,不推荐使;
法2:在需要默认横屏的界设置,进时强制横屏,离开时强制竖屏
关于这种使,这个具体可以参考第五节中的 demo2
注意:两种法不可同时使
、关于旋转后的适配问题
屏幕旋转的实现会带来相应的 UI 适配问题,我们需要针对不同向下的界重新调整视图布
局。先我们要能够监测到屏幕旋转事件,这分为两种情况:
1.视图控制器UIViewController的监测
当发转屏事件的时候,下的 UIViewControoller 法会监测到视图 View 的变化,从
帮助我们适配
- (void)viewWillTransitionToSize:(CGSize)size 
withTransitionCoordinator:(id )coordinator NS_AV
从注释可以看出此法在屏幕旋转的时候被调,我们使时候也应该先调 super
法,具体代码使示例如下:
//屏幕旋转之后,屏幕的宽互换,我们借此判断重新布局
//横屏:size.width > size.height
//竖屏: size.width < size.height
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
if (size.width > size.height) {
//横屏设置,为防遮挡键盘,调整输视图的度
self.textView_height.constant = 50; }else{
//竖屏设置
self.textView_height.constant = 200; } }
2.视图横竖屏监测 
如果是类似于表视图的单元格,要监测到屏幕变化实现适配,我们需要到 layoutSubviews
法,因为屏幕切换横竖屏时会触发此法,然后我们根据状态栏的位置就可以判断横竖屏
了,代码示例如下:
- (void)layoutSubviews { [super layoutSubviews];
//通过状态栏电池图标判断横竖屏
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationMaskPortrait
//竖屏布局
} else {
//横屏布局
} }
九、APP启动即全屏
有时项需要从 App 启动就默认是横屏,这有个很便的法,就是我们在 Device
Orientation 属性配置设置如下:
但是只这样处理的话,会让项只持横屏,所以我们可以在 Appdelegate 再次调整我们所
持的向,法已经说过,这就不累述了;




0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有