iOS屏幕旋转与锁屏

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

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

iOS-旋转.gif

主要涉及以下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是两个互不相干的属性,通常情况下会一起出现,在这里正好利用此特性在屏幕旋转后进行重新布局。

  • 第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 屏幕方向那点事儿

你可能感兴趣的:(iOS屏幕旋转与锁屏)