iOS屏幕旋转---玩起来

9,930 阅读7分钟

iOS屏幕旋转一直是一个清晰又模糊的概念,看似简单,但是对接过程中总会遇到这样那样的问题,比如屏幕闪烁、旋转不生效、状态栏消失等等一系列令人恶心的bug。笔者负责的项目,属于Reactnative&&ObjectC混合开发模式,目前的需要需要主app默认竖向展示,同时提供给Reactnative一个支持自动横竖屏的ViewController。特此针对iOS屏幕旋转进行了整理归纳,望对各位开发者有所帮助。

一、令人恶心的三种枚举解析

1、设备方向(物理旋转) - UIDeviceOrientation

  • 硬件设备(ipad、iPhone)背身的旋转方向,以Home为参照物
//Portrait 表示纵向,Landscape 表示横向。
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
     UIDeviceOrientationUnknown,

     // Device oriented vertically, home button on the top
     UIDeviceOrientationPortraitUpsideDown, 

     // Device oriented horizontally, home button on the right
     UIDeviceOrientationLandscapeLeft,      

    // Device oriented horizontally, home button on the left
     UIDeviceOrientationLandscapeRight,     

    // Device oriented flat, face up
    UIDeviceOrientationFaceUp,             

    // Device oriented flat, face down
    UIDeviceOrientationFaceDown            
} __TVOS_PROHIBITED;
  • 设备的旋转方向只读不可写
获取当前屏幕的方法:[UIDevice currentDevice].orientation
  • 设备旋转监听
[[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程序界面的当前旋转方向(可以设置)
// Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa).
// This is because rotating the device to the left requires rotating the content to the right.
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {

     UIInterfaceOrientationUnknown               = UIDeviceOrientationUnknown,

     UIInterfaceOrientationPortrait              = UIDeviceOrientationPortrait,

     UIInterfaceOrientationPortraitUpsideDown    = UIDeviceOrientationPortraitUpsideDown,

     UIInterfaceOrientationLandscapeLeft         = UIDeviceOrientationLandscapeRight,

     UIInterfaceOrientationLandscapeRight        = UIDeviceOrientationLandscapeLeft

    } __TVOS_PROHIBITED;
  • 程序界面的方向使用UIInterfaceOrientation,与设备旋转方向没有任何关系
为了达到页面的流畅效果
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, 
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

3、页面旋转聚合(iOS6 后出现的)- 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;

二、单页面旋转方向设置

  • 实现方法
//方法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(6_0) __TVOS_PROHIBITED;!
  • 方法解析
方法1:页面是否支持自动旋转
方法2:页面支持的旋转方向有哪些
方法3:页面优先展示的旋转方向

三、两种屏幕旋转的触发方式

1、系统未关闭屏幕自动旋转功能

//1.决定当前界面是否开启自动转屏,如果返回NO,后面两个方法也不会被调用,只是会支持默认的方向
- (BOOL)shouldAutorotate {
      return YES;
}

//2.返回支持的旋转方向
//iPad设备上,默认返回值UIInterfaceOrientationMaskAllButUpSideDwon
//iPad设备上,默认返回值是UIInterfaceOrientationMaskAll
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
     return UIInterfaceOrientationMaskAll;
}

//3.返回进入界面默认显示方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
     return UIInterfaceOrientationPortrait;
}

2、系统关闭屏幕自动旋转功能

在程序界面通过点击等方式切换到横屏

// 方法1:
- (void)setInterfaceOrientation:(UIDeviceOrientation)orientation {
      if ([[UIDevice currentDevice]   respondsToSelector:@selector(setOrientation:)]) {
          [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:orientation]     
                                       forKey:@"orientation"];
        }
    }

//方法2:
- (void)setInterfaceOrientation:(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]];
            int val = orientation;
            [invocation setArgument:&val atIndex:2];
            [invocation invoke];
        }
    }
  • 确保shouldAutorotate方法返回YES
  • 两者使用的参数类型不

四、屏幕旋转控制的优先级

1、屏幕旋转设置方式

- Xcode的General设置
- Xcode的nfo.plist设置
- 代码设置Appdelegete中

2、旋转优先级

工程Target属性配置(全局权限) > Appdelegate&&Window > 根视图控制器> 普通视图控制器

3、开启App旋转的全局权限

  • Device Orientation属性配置
【General】—>【Deployment Info】—>【Device Orientation】
值得注意的是,对于iPhone,如果四个属性我们都选或者都不选,效果和默认的情况一样  
  • Info.Plist设置
Supported interface orientation
与第一种方式一样的效果,两种方式最终都是设置info.plist中的属性
  • Appdelegate&&Window中设置
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {

    return  UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;

}

如果我们实现了Appdelegate的这一方法,那么我们的App的全局旋转设置将以这里的为准,即使前两种方法的设置与这里的不同。

五、设置单页面的旋转权限

开发中常涉及到的控制器

UITabbarViewController,UINavigationBarController ,UIViewController

我们的项目都是用UITabbarViewController作为Window的根视图控制器,然后管理着若干个导航控制器UINavigationBarController,再由导航栏控制器去管理普通的视图控制器UIViewController。若以此为例的话,关于旋转的优先级从高到低就是UITabbarViewController>UINavigationBarController >UIViewController了。如果具有高优先级的控制器关闭了旋转设置,那么低优先级的控制器是无法做到旋转的。

比如说我们设置要单个视图控制器可以自动旋转,这需要在视图控制器中增加shouldAutorotate方法返回YES或者NO来控制。但如果存在上层根视图控制器,而我们只在这个视图控制器中实现方法,会发现这个方法是不走的,因为这个方法被上层根视图控制器拦截了

六、实现自动可控的旋转

1、逐级实现

  • 1、UITabbarViewController
//是否自动旋转
-(BOOL)shouldAutorotate{
    return self.selectedViewController.shouldAutorotate;
}

//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.selectedViewController supportedInterfaceOrientations];
}

//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
  • 2、UINavigationController
//是否自动旋转
//返回导航控制器的顶层视图控制器的自动旋转属性,因为导航控制器是以栈的原因叠加VC的
//topViewController是其最顶层的视图控制器,
-(BOOL)shouldAutorotate{
    return self.topViewController.shouldAutorotate;
}

//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.topViewController supportedInterfaceOrientations];
}

//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return [self.topViewController preferredInterfaceOrientationForPresentation];
}

其实就是高优先级的视图控制器要跟随低优先级控制器的旋转配置。这样就能够达到目的

2、使用模态视图

使用模态视图可以不受这种根视图控制器优先级的限制。这个也很容易理解,模态弹出的视图控制器是隔离出来的,不受根视图控制的影响。具体的设置和普通视图器代码相同

七、实现需求

App主要主界面竖向展示,部分页面横向展示

两种解决方案

1、逐级控制

  • 步骤方式
1.开启全局权限设置项目支持的旋转方向
2.自定义标签控制器和导航控制器来设置屏幕的自动旋转。
3.自定义基类控制器设置不支持自动转屏,并默认只支持竖屏
4.对项目中需要转屏幕的控制器开启自动转屏、设置支持的旋转方向并设置默认方向

2、通过全局监听当前的方向变化

  • 步骤方式
1.在Applegate文件中增加一个用于记录当前屏幕是否横屏的属性
2.需要横屏的界面,进入界面后强制横屏,离开界面时恢复竖屏
  • 核心代码
1、添加监听

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

2、实现监听

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if (_allowAutoRotate) {
        //只支持横屏
        return UIInterfaceOrientationMaskLandscape;
    }else{
        //支持竖屏
        return UIInterfaceOrientationMaskPortrait;
    }
}

3、页面控制

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
    delegate.allowAutoRotate = YES;
    //进入界面:设置横屏
    [self setDeviceInterfaceOrientation:UIDeviceOrientationLandscapeLeft];
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
    delegate.allowAutoRotate = NO;
    //离开界面:设置竖屏
    [self setDeviceInterfaceOrientation:UIDeviceOrientationPortrait];
}

八、优化显示

视图切换显示异常问题

当前viewController达到预期效果,但是在返回上一页时,或者在当前页面不不支持的方向的上一页进来时,不能立即达到预期状态,需要设备方向更换一次才能恢复正常

#pragma mark -UITabBarControllerDelegate
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
    [self presentViewController:[UIViewController new] animated:NO completion:^{
        [self dismissViewControllerAnimated:NO completion:nil];
    }];
}
#pragma mark -UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    [self presentViewController:[UIViewController new] animated:NO completion:^{
        [self dismissViewControllerAnimated:NO completion:nil];
    }];
}

但是会出现屏幕闪烁问题,不推荐使用此方案,可以通过强制设置屏幕方向,在视图的钩子函数中进行手动控制

屏幕旋转后,状态栏无法显示

  • 设置info.plist 中 View controller-based status bar appearance YES
  • 对应的控制其中实现下面代码

//设置样式
- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}
 
//设置是否隐藏
- (BOOL)prefersStatusBarHidden {
//    [super prefersStatusBarHidden];
    return NO;
}
 
//设置隐藏动画
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
    return UIStatusBarAnimationNone;
}

原文链接: tech.meicai.cn/detail/83, 也可微信搜索小程序「美菜产品技术团队」,干货满满且每周更新,想学习技术的你不要错过哦。