iOS屏幕旋转与锁屏

在做视频开发时遇到屏幕旋转问题,其中涉及到 StatusBar、 UINavigationController、UITabBarController 、UIViewcontroller

在设备锁屏下的整体效果图

主要涉及以下4点:
- 横竖屏的旋转
- 屏幕旋转相应改变视图位置
- 旋转时状态栏的隐藏与显示
- 锁屏

1、横竖屏旋转

  • 第1步:

     -(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    
    //    NSLog(@"0000000---------%@",NSStringFromClass([[self topViewController] class]));
    //    if ([NSStringFromClass([[self topViewController] class]) isEqualToString:@"FirstViewController"]) {
           
    //        //横屏
    //        return UIInterfaceOrientationMaskLandscapeRight;
    //    }
    //    //竖屏
    //    return UIInterfaceOrientationMaskPortrait;
    
        NSUInteger orientations = UIInterfaceOrientationMaskAllButUpsideDown;
    
        if(self.window.rootViewController){
            //取出当前显示的控制器
            UIViewController *presentedViewController = [self topViewControllerWithRootViewController:self.window.rootViewController];
            //按当前控制器支持的方向确定旋转方向(将旋转方向重新交给每个控制器自己控制)
            NSLog(@"%s, line = %d",__FUNCTION__,__LINE__);
            orientations = [presentedViewController supportedInterfaceOrientations];
        }
    
        return orientations;
    }
    //获取界面最上层的控制器
    //- (UIViewController*)topViewController {
           
    //    NSLog(@"%s, line = %d",__FUNCTION__,__LINE__);
    //    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
    //}
    //一层一层的进行查找判断
    - (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
        NSLog(@"%s, line = %d",__FUNCTION__,__LINE__);
        if ([rootViewController isKindOfClass:[UITabBarController class]]) {
    
            UITabBarController* tabBarController = (UITabBarController*)rootViewController;
            NSLog(@"Tabbar:%@",NSStringFromClass([tabBarController.selectedViewController class]));
    
            return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
        } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
    
            UINavigationController* nav = (UINavigationController*)rootViewController;
            NSLog(@"nav:%@",NSStringFromClass([nav.visibleViewController class]));
            return [self topViewControllerWithRootViewController:nav.visibleViewController];
        } else if (rootViewController.presentedViewController) {
            NSLog(@"present:%@",NSStringFromClass([rootViewController.presentationController class]));
            UIViewController* presentedViewController = rootViewController.presentedViewController;
            return [self topViewControllerWithRootViewController:presentedViewController];
        } else {
            NSLog(@"root:%@",rootViewController);
            return rootViewController;
        }
    }
    

    代码中通过 -(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window 方法将控制器交给自己控制,该方法默认值为Info.plist中配置的Supported interface orientations项的值。

  • 第2步:在各控制器设置支持的方向

    //是否允许旋转(默认允许)
    - (BOOL)shouldAutorotate {
      return YES;
    }
    
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
      //允许旋转的方向
      return UIInterfaceOrientationMaskAll;
    }

    其中- supportedInterfaceOrientations 方法在 iPad 中默认取值为 UIInterfaceOrientationMaskAll,即默认支持所有屏幕方向;而 iPhone 跟 iPod Touch 的默认取值为 UIInterfaceOrientationMaskAllButUpsideDown,即支持除竖屏向下以外的三个方向。
    在设备屏幕旋转时,系统会调用 - shouldAutorotate 方法检查当前界面是否支持旋转,只有 - shouldAutorotate返回 YES 的时候,- supportedInterfaceOrientations 方法才会被调用,以确定是否需要旋转界面。
    这个是 TabbarController 中设置的,它会影响关联的 UIViewController 的支持方向,需要在 UIViewController 中进一步设置

    //此方法来控制能否横竖屏 控制锁屏
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        NSLog(@"%s, line = %d",__FUNCTION__,__LINE__);
        UIInterfaceOrientationMask inter;
        if (_lockScreen) {
            switch (_lockOrientation) {
                case 1:
                    inter = UIInterfaceOrientationMaskPortrait;
                    break;
                case 2:
                    inter = UIInterfaceOrientationMaskPortraitUpsideDown;
                    break;
                case 3:
                    inter = UIInterfaceOrientationMaskLandscapeRight;
                    break;
                case 4:
                    inter = UIInterfaceOrientationMaskLandscapeLeft;
                    break;
                default:inter = UIInterfaceOrientationMaskAll;
                    break;
            }
        } else {
            inter = UIInterfaceOrientationMaskAll;
        }
        //支持全部方向
        return inter;
    }
  • 第3步:强制转换控制器方向

    - (void)setInterOrientation:(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;
            // 从2开始是因为0 1 两个参数已经被selector和target占用
            [invocation setArgument:&val atIndex:2];
            [invocation invoke];
        }
    }

    这样就可以完成横竖屏的切换。

2、屏幕旋转相应改变视图位置

这里先扩展UIDeviceOrientation & UIInterfaceOrientation的知识

  • UIDeviceOrientation 设备的物理方向
    UIDeviceOrientation即我们手持的移动设备的Orientation,是一个三围空间,有六个方向,通过[UIDevice currentDevice].orientation获取当前设备的方向。

    typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
      UIDeviceOrientationUnknown,
      UIDeviceOrientationPortrait,            
      UIDeviceOrientationPortraitUpsideDown,  // Device oriented vertically, home button on the top 竖屏向下,即头在下,Home 键在上
      UIDeviceOrientationLandscapeLeft,       // Device oriented horizontally, home button on the right 横屏头在左,Home键在右
      UIDeviceOrientationLandscapeRight,      // Device oriented horizontally, home button on the left  横屏头在右,Home键在左
      UIDeviceOrientationFaceUp,              // Device oriented flat, face up
      UIDeviceOrientationFaceDown             // Device oriented flat, face down
    } ;
  • UIInterfaceOrientation界面的显示方向

    UIInterfaceOrientation即我们看到的视图的Orientation,可以理解为statusBar所在的方向,是一个二维空间,有四个方向, 通过[UIApplication sharedApplication].statusBarOrientation即状态栏的方向获取当前界面方向。

    // 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
    } 
  • UIInterfaceOrientationMask支持的方向

    // iOS 6 之后用于控制界面的枚举值
    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),
    }

    由上可以发现:

    1. iOS 6 及之后版本使用的UIInterfaceOrientationMask类型来控制屏幕屏幕方向,该类型也新增加了几个枚举取值,可用一个枚举取值来代表多个屏幕方向,使用起来更方便。
    2. 注意在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 ,大意是界面的左转相当于设备的右转,如果设备向左转时就需要内容(即界面)向右转。即:
      UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight
      UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
      下面还会举例说明。

其实UIDeviceOrientationUIInterfaceOrientation是两个互不相干的属性,通常情况下会一起出现,在这里正好利用此特性在屏幕旋转后进行重新布局。

4、锁屏

锁屏时,不管系统锁屏是否关闭、Push 或 Present 返回后,界面依然保持不变。

  • 第1步:监听 UIDeviceOrientationDidChangeNotification状态

    //监听设备旋转 改变 视图 对应位置
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
    
    //用来控制横竖屏时调整视图位置
    - (void)deviceOrientationDidChange
    {
      [self isPortrait]; 
    }
  • 第2步:重新布局

    if (_interOrientation == UIInterfaceOrientationPortrait || _interOrientation == UIInterfaceOrientationPortraitUpsideDown) {
            self.top.constant = 145;
            self.bottom.constant = 210;
    
        } else if (_interOrientation == UIInterfaceOrientationLandscapeRight || _interOrientation == UIInterfaceOrientationLandscapeLeft) {
            self.top.constant = 40;
            self.bottom.constant = 50;
        }

    例如:竖屏转横屏
    界面竖屏UIInterfaceOrientationPortrait ->横屏UIInterfaceOrientationLandscapeRight,设备方向UIDeviceOrientationPortrait->UIDeviceOrientationLandscapeLeft,在设备发生变化这个过程触发UIDeviceOrientationDidChangeNotification监听,然后进行重新布局。

3、旋转时状态栏的隐藏与显示

  • 这里只记述旋转时状态栏的变化,由竖屏想横屏变化时状态栏会消失。

    //在需要的`UIViewController`设置是否隐藏
    - (BOOL)prefersStatusBarHidden {
    NSLog(@"%s, line = %d",__FUNCTION__,__LINE__);
    return NO;
    }

4、锁屏

锁屏时,不管系统锁屏是否关闭、Push 或 Present 返回后,界面依然保持不变。

  • 第1步:设置锁屏

    - (IBAction)lockAction:(UIButton *)sender {
        if (_lockScreen) {
    
            _lockScreen = NO;
            [sender setTitle:@"锁定屏幕" forState:UIControlStateNormal];
        } else {
            _lockScreen = YES;
    
            [sender setTitle:@"解开屏幕" forState:UIControlStateNormal];
        }
        _lockOrientation = _interOrientation;
    }
  • 第2步:绕过强转

    - (void)interfaceOrientation:(UIInterfaceOrientation)orientation
    {
    
        [self isPortrait];
        //锁屏情况下 不旋转
        if (!_lockScreen) {
            [self setInterOrientation:orientation];
        }
  • 第3步:针对 Push 或 Present 返回后

    - (void)viewWillAppear:(BOOL)animated {
    
        if (_lockScreen) {
            //记录返回时的界面状态
            [self setInterOrientation:_lockOrientation];
        } else {
          [self isPortrait];
        }
    }

5、 针对特定UIViewController方向的支持

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

        if ([NSStringFromClass([[self topViewController] class]) isEqualToString:@"FirstViewController"]) {
            //横屏
            return UIInterfaceOrientationMaskLandscapeRight;
        }
        //竖屏
        return UIInterfaceOrientationMaskPortrait;
    }

最后的献上GitHub代码,还有2个小的 bug ,有兴趣的朋友欢迎来探讨。

- 参考文章:

iOS 屏幕旋转问题总结
iOS 屏幕方向那点事儿

你可能感兴趣的