iOS 屏幕旋转之横屏(landscape)和竖屏(portrait)

一,前言

(1)何为横屏/竖屏
        
                                                                                                             横屏

     

竖屏:

2)是否启动横屏/竖屏切换的区别
        App开启横/竖屏切换:
             开启横竖屏时,当屏幕为横屏时,系统window界面会以横屏的左上角为坐标系原点;当屏幕为竖屏时window界面会以竖屏的左上角为坐标系原点。横竖屏切换,坐标系切换。
                                          
        App关闭横/竖屏切换:
            关闭横竖屏时,当屏幕为横屏时,系统window界面会以横屏的左上角为坐标系原点;当屏幕为竖屏时window界面会以竖屏的左上角为坐标系原点。横竖屏切换,坐标系不会切换,系统会以屏幕的初时坐标系为坐标原点。

(3)UIKit处理屏幕旋转的流程 

  当加速计检测到方向变化的时候,会发出 UIDeviceOrientationDidChangeNotification 通知,这样任何关心方向变化的view都可以通过注册该通知,在设备方向变化的时候做出相应的响应。

  UIKit的响应应屏幕旋转的流程如下

1、设备旋转的时候,UIKit接收到旋转事件。
2、UIKit通过AppDelegate通知当前程序的window。
3、Window会知会它的rootViewController,判断该view controller所支持的旋转方向,完成旋转。
4、如果存在弹出的view controller的话,系统则会根据弹出的view controller,来判断是否要进行旋转。

      UIViewController实现屏幕旋转如下

  在响应设备旋转时,我们可以通过UIViewController的方法实现更细粒度的控制,当view controller接收到window传来的方向变化的时候,流程如下:

  1、首先判断当前viewController是否支持旋转到目标方向,如果支持的话进入流程2,否则此次旋转流程直接结束。

  2、调用 willRotateToInterfaceOrientation:duration: 方法,通知view controller将要旋转到目标方向。如果该viewController是一个container view controller的话,它会继续调用其content view controller的该方法。这个时候我们也可以暂时将一些view隐藏掉,等旋转结束以后在现实出来。

  3、window调整显示的view controller的bounds,由于view controller的bounds发生变化,将会触发 viewWillLayoutSubviews 方法。这个时候self.interfaceOrientation和statusBarOrientation方向还是原来的方向。

  4、接着当前view controller的 willAnimateRotationToInterfaceOrientation:duration: 方法将会被调用。系统将会把该方法中执行的所有属性变化放到动animation block中。

  5、执行方向旋转的动画。

  6、最后调用 didRotateFromInterfaceOrientation: 方法,通知view controller旋转动画执行完毕。这个时候我们可以将第二部隐藏的view再显示出来。

     整个响应过程如下图所示:

 


二,如何设置横竖屏切换

(1)横竖屏方向枚举

       横竖屏的三种枚举,UIInterfaceOrientationUIInterfaceOrientationMaskUIDeviceOrientation
       * UIInterfaceOrientation    当前页面的方向 (以home在下为正竖屏方向,home键在右为左横屏方向)

    typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
       UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown, 
      UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait, //正竖屏方向
      UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, //反竖屏方向
      UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight, //左横屏方向
      UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft   //右横屏方向
   }

        *  UIDeviceOrientation是设备的当前所处的方向,而且事实上它有6个值,

  typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
      UIDeviceOrientationUnknown,   //未知方向
      UIDeviceOrientationPortrait,  //竖直          // Device oriented vertically, home button on the bottom
      UIDeviceOrientationPortraitUpsideDown,  // 上下反转
      UIDeviceOrientationLandscapeLeft,       // 向左旋转
      UIDeviceOrientationLandscapeRight,      // 向右旋转
      UIDeviceOrientationFaceUp,              // 屏幕朝上
      UIDeviceOrientationFaceDown             // 屏幕朝下
  }

⚠️注意:
请仔细观察上面的枚举值。

在处于竖屏和上下翻转的状态下这两个枚举值是一样的,而当处于横屏时,这两个值刚好相反。

      所以在有时你发现跟你预期的翻转方向不一样的时候,可能你用错了枚举。

⚠️总结:在设备进行横屏旋转的时候,为了横屏时上下不翻转,所以当Device处于Left时,界面应该是Right旋转。这样才不会使横屏时内容上下翻转。所以我想你应该明白了为什么在处于横屏时为什么他们俩的值是刚好相反的。

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

(2)开启横竖屏权限

        第一种是在项目中直接进行勾选:

                   


可以看到这种勾选方式允许你进行四个方向的配置,并且这种勾选方式会直接在你的项目plist文件中添加 (二者设置任何一方都会同步到另一方)


 

但是由于在这里配置是对项目启动时lanuch界面产生影响,而往往我们又没有对lanuch进行横竖屏适配,所以在这个时候我们就需要使用第二种方式进行配置。

  第二种:在项目中的AppDelegate文件中进行配置。

#pragma mark - InterfaceOrientation //应用支持的方向
 - (UIInterfaceOrientationMask)application:(UIApplication *)application 
supportedInterfaceOrientationsForWindow:(UIWindow *)window {
     return UIInterfaceOrientationMaskAllButUpsideDown;
}

  搭配UIInterfaceOrientationMask使用,你可以很方便的让你项目开启你所需要的横竖屏权限和限制条件。

  第三种:在VC中如何开启横竖屏
                 一般我们项目中不需要全部横屏,仅支持竖屏,比如列表页;有些页面必须支持横竖屏,比如视频播放页。这个时候我们就需要对相应的控制器做细致化操作。
            

  (1)个别界面固定方向,其他所有界面都支持横竖屏切换 (其它方向相同)
    这种情况,在【General】-->【Device Orientation】中设置好支持的方向后,只需要在这些特殊的视图控制器中重写两个方法:

// 支持设备自动旋转
- (BOOL)shouldAutorotate {
    return YES;
  }

/** 
*  设置特殊的界面支持的方向,这里特殊界面只支持Home在右侧的情况
*/
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight;
 }

     (2)个别界面支持横竖屏切换,其他所有界面都固定方向

    可能大多数App会是这种需求,某些特殊界面只能横屏,如视频播放类App。
这里有两种处理方式:
  方式一:通过创建基类实现
    在【General】-->【Device Orientation】中设置好需要支持的所有方向。然后使用一个基类控制器,在基类控制器中重写两个控制横竖屏的方法:  

// 支持设备自动旋转
- (BOOL)shouldAutorotate {
    return YES;
}

// 支持竖屏显示
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

再然后,特殊的界面上再重写这俩方法,让其可以自动切换方向。

// 如果需要横屏的时候,一定要重写这个方法并返回NO
- (BOOL)prefersStatusBarHidden {
    return NO;
}

// 支持设备自动旋转
- (BOOL)shouldAutorotate {
    return YES;
}

// 支持横屏显示
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    // 如果该界面需要支持横竖屏切换
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait;
    // 如果该界面仅支持横屏
    // return UIInterfaceOrientationMaskLandscapeRight;
}

  方式二 借助通知来控制界面的横竖屏切换
    用方式一的方法,还需要借助一个基类,所有的控制器都要继承这个基类,感觉太麻烦。
    还是整个App中大部分界面都是竖屏,某个界面可以横竖屏切换的情况。

    首先,在【General】-->【Device Orientation】设置仅支持竖屏,like this:


Device Orientation

然后在特殊的视图控制器里的ViewDidLoad中注册通知:

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

通知方法的实现过程:

// 用到的两个宏:
  #define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)     
  #define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)
- (void)deviceOrientationDidChange {
    if([UIDevice currentDevice].orientation == UIDeviceOrientationPortrait) {
        [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
        [self orientationChange:NO];
        //注意: UIDeviceOrientationLandscapeLeft 与 UIInterfaceOrientationLandscapeRight
    } else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
        [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
        [self orientationChange:YES];
    }
}

- (void)orientationChange:(BOOL)landscapeRight {
    if (landscapeRight) {
        [UIView animateWithDuration:0.2f animations:^{
            self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
            self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        }];
    } else {
        [UIView animateWithDuration:0.2f animations:^{
            self.view.transform = CGAffineTransformMakeRotation(0);
            self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        }];
    }
}

最重要的一点:
需要重写如下方法,并且返回NO。

- (BOOL)shouldAutorotate {
    return NO;
}

这样,在设备出于横屏时,界面就会变成横屏,设备处于竖屏时,界面就会变成竖屏。

三.横竖屏控制优先级

  对于限于VC范围来讲优先级最高的是当前的windowrootViewController,而往往我们的项目结构是容器视图控制器控制VCtabBarController控制navigationController之后是VC,而横竖屏控制的优先级也是跟你的项目架构一样。而且是一旦优先级高的关闭了横竖屏配置,优先级低的无论如何配置都无法做到横竖屏。所以在你接受这个需求的时候,你需要看一下根视图的配置。

对于这种情况,我们有两种处理方式,一种是通过模态的方式跳转到下个VC,这个VC是隔离出来的,不在你之前的容器里,不会受到rootViewController的影响。

而另一种我们需要改造一下根视图的配置:

-(BOOL)shouldAutorotate {  
    return [[self.viewControllers lastObject] shouldAutorotate];  
}  

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {  
     return [[self.viewControllers lastObject] supportedInterfaceOrientations];  
}  

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {  
    return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];  
}

或者

-(BOOL)shouldAutorotate {
    if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
        return YES;
    }
    return NO;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
        return UIInterfaceOrientationMaskLandscapeLeft;
    }
    return UIInterfaceOrientationMaskPortrait;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
        return UIInterfaceOrientationLandscapeLeft;
    }
    return UIInterfaceOrientationPortrait;
} 

可以看到我们通过获取push栈中的最后一个VC的属性,或指定特殊的VC来进行rootViewController的横竖屏设置。

当然也可以通过NSNotificationCenter或者NSUserDefaults的方式对这里的值进行设置,在这里我就不过多赘述了。

总之要知道优先级的问题,general== appDelegate>> rootViewController>> nomalViewController

明白了权限的优先级以及开启的方法我想转屏就很显而易见了。

四.注意事项和建议

1)注意事项

  当我们的view controller隐藏的时候,设备方向也可能发生变化。例如view Controller A弹出一个全屏的view controller B的时候,由于A完全不可见,所以就接收不到屏幕旋转消息。这个时候如果屏幕方向发生变化,再dismiss B的时候,A的方向就会不正确。我们可以通过在view controller A的viewWillAppear中更新方向来修正这个问题。

2)屏幕旋转时的一些建议

  • 在旋转过程中,暂时界面操作的响应。
  • 旋转前后,尽量保持当前显示的位置不变。
  • 对于view层级比较复杂的时候,为了提高效率在旋转开始前使用截图替换当前的view层级,旋转结束后再将原view层级替换回来。
  • 在旋转后最好强制reload tableview,保证在方向变化以后,新的row能够充满全屏。例如对于有些照片展示界面,竖屏只显示一列,但是横屏的时候显示列表界面,这个时候一个界面就会显示更多的元素,此时reload内容就是很有必要的。




posted on 2018-11-07 04:02  梁飞宇  阅读(6357)  评论(0编辑  收藏  举报