iOS屏幕旋转解决方案

1.导航控制器栈内部的VC方向是导航控制器来决定的。nav --- A --- B --- C,C的旋转方法是不起做用的,靠的是nav的-(BOOL)shouldAutorotate-(UIInterfaceOrientationMask)supportedInterfaceOrientations

解决方案是:重写nav的旋转方法,把结果指向到topViewController:bash

-(BOOL)shouldAutorotate{
   return self.topViewController.shouldAutorotate;
}

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return self.topViewController.supportedInterfaceOrientations;
}
复制代码

对于UITabBarController,就转嫁为它的selectedViewController的结果。app

2.旋转的逻辑流是:手机方向改变了 ---> 通知APP ---> 调用APP内部的关键VC(TabBar或Nav)的旋转方法 ---> 获得可旋转而且支持当前设备方向 ---> 旋转到指定方向。

逻辑流的初始时物理上手机方向改变了。全部若是A push到 B,A只支持竖屏,而B只支持横屏,若是这时手机物理方向没变,那么B仍是会跟A同样竖屏,哪怕它只支持横屏而且问题1也解决了的。测试

解决方案:强制旋转。ui

@implementation UIDevice (changeOrientation)

+ (void)changeInterfaceOrientationTo:(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];
   }
}


@end
复制代码

UIDevice提供一个category,调用setOrientation:这个私有方法来实现。spa

3.有了问题1和2的解决,对于整个项目的基本方案肯定。通常项目会有一个主方向,绝大多数界面都是这个方向,好比竖屏,而后有特定界面是特定方向。

那么解决方案是:设计

  • 在target --> General --> Development Info里配置支持全部可能的方向
  • 使用baseViewController,项目全部VC都继承与它,在baseVC里写入默认方向设置,这个默认设置就是绝大多数界面支持的方向。
  • 而后在特殊方向界面,重写-(BOOL)shouldAutorotate-(UIInterfaceOrientationMask)supportedInterfaceOrientations来达到本身的目的。
  • 特殊界面由于要强制旋转,因此在进入界面是旋转到须要方向:
-(void)viewWillAppear:(BOOL)animated{
   [UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationLandscapeLeft)];
}
复制代码
4.push和pop的结果测试:

要验证问题3的方案是否知足须要,知足须要的意思是:每一个页面可以显示它支持的方向并且不会干扰到其余界面。因此测试一下push和pop的状况。code

测试变量有:继承

  • 当前的界面是默认仍是特殊,这里把只有竖屏设为默认,横屏为特殊状况。默认表明这个VC只需继承baseVC的方向相关方法,不作任何额外处理。
  • 界面是push仍是pop
  • 下一个界面是默认状况仍是特殊状况。
  • 下一个界面的shouldAutorate是否为YES。

先后两个界面方向一致的画,结果确定是好的,就不测试 了。最终测试结果以下:get

动做 当前 目标 目标可旋转 结果
push 默认 特殊 ✔️ 成功
push 默认 特殊 失败
push 特殊 默认 ✔️ 失败(2)
push 特殊 默认 失败(3)
pop 默认 特殊 ✔️ 成功
pop 默认 特殊 失败
pop 特殊 默认 ✔️ 成功(1)
pop 特殊 默认 成功(1)
  • 成功表明目标界面旋转到了指望的方向
  • 标记1:没有旋转,直接显示的默认样式(竖屏)。
  • 标记2:从横屏到竖屏,没有切换方向,为何?由于UIDevice的方向没有修改,没有触发切换效果。因此在特殊界面离开的时候还要调用强制旋转。其实只要相邻的方向不一样,就要在切换时触发强制旋转。
  • 添加了viewWillDisappear里的强制旋转后,标记2能够解决。但标记3仍是失败,其实push时,下一个界面若是是不可旋转的,那么方向必定是不变了。

特殊界面只要保持,进入和离开时都调用强制旋转,而且自身shouldAutorate为YES,那么push或pop进入特殊界面都没有问题。关键是从特殊界面离开进入默认界面,pop时是成功的,push时若是默认界面是不可旋转的,就会失败。string

针对这个有两种方案:

  • 在离开前把当前界面旋转为默认,先旋转,再push。
  • 把默认界面改成可旋转。
5.特殊方向界面离开前先旋转到默认

由于特殊界面支持的方向不包含默认方向,因此只是强制旋转时不起做用的,在强制旋转前还要修改支持的方向。具体代码:

- (IBAction)push:(id)sender {
   
   [self changeOrientationBeforeDisappear];  //离开前先修改方向,其余每一个出口都要调用这个方法。不能在`viewWillDisappear`里调用,由于这时push等已经触发了
   TFThirdViewController *thirdVC = [[TFThirdViewController alloc] init];
   [self.navigationController pushViewController:thirdVC animated:YES];
}

-(void)changeOrientationBeforeDisappear{
   _orientation = UIInterfaceOrientationMaskPortrait;  //替换为默认方向
   [UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationPortrait)];
   _orientation = UIInterfaceOrientationMaskLandscapeLeft; //替换为特殊方向界面自身须要的方向
}

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
   return _orientation;  //根据变量变化而变化
}

复制代码

若是下一个界面不是默认,会是什么状况?会有两次旋转。离开时旋转到默认,进入下一个界面,它自身又旋转到指定方向。效果很差,若是想一次到位,怎么办?就要离开的时候知道下一个界面指望的方向是什么,而后preferredInterfaceOrientationForPresentation正好符合这个意图。 因此修改成:

@interface TFSecondViewController (){
   UIInterfaceOrientationMask _orientation;
   UIInterfaceOrientationMask _needOrientation;
}

@end

@implementation TFSecondViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   
   _needOrientation = UIInterfaceOrientationMaskLandscapeLeft;
   _orientation = _needOrientation;
}

-(void)viewWillAppear:(BOOL)animated{
   [UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationLandscapeLeft)];
}

-(BOOL)shouldAutorotate{
   return YES;
}

- (IBAction)push:(id)sender {
   
   TFThirdViewController *thirdVC = [[TFThirdViewController alloc] init];
   [self changeOrientationBeforeDisappearTo:thirdVC];  //离开前先修改方向,其余每一个出口都要调用这个方法。不能在`viewWillDisappear`里调用,由于这时push等已经触发了
   
   [self.navigationController pushViewController:thirdVC animated:YES];
}

-(void)changeOrientationBeforeDisappearTo:(UIViewController *)nextVC{
   _orientation = UIInterfaceOrientationMaskAll;  //改成任意方向
   [UIDevice changeInterfaceOrientationTo:[nextVC preferredInterfaceOrientationForPresentation]];
   _orientation = _needOrientation; //替换为特殊方向界面自身须要的方向
}

-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
   return _orientation;  //根据变量变化而变化
}

@end

复制代码

_needOrientation时当前页面须要的样式。

总结起来就是:

  • 给绝大多数状况建一个baseVC,里面设置默认方向。
  • 对特殊方向界面:
    • 进入时(viewWillAppear)强制旋转到须要的方向
    • 离开时,注意并非viewWillDisappear,而是push操做以前,先修改方向为下一个界面的指望方向。
    • 固然自身的shouldAutorotate保持为YES。
  • 方向相关的3个方法所有要实现。由于基类(BaseVC)作了处理,能够省去绝大部分的工做。特殊方向的界面单个处理便可。
  • preferredInterfaceOrientationForPresentation的方向要和进入时的方向一致,这样就不会有2次旋转。

相比把基类的shouldAutorotate改成YES,这个方案的好处是,把特殊状况的处理基本都压缩在特殊界面自身内部了,依赖的只有其余界面的supportedInterfaceOrientations,这个方法是一个补充性的,不会干扰其余界面本来的设计。而对shouldAutorotate却比较麻烦,由于其余界面可能不但愿旋转。

再次测试pop和push状况:

动做 当前 目标 目标可旋转 结果
push 默认 特殊 ✔️ 成功
push 特殊 默认 ✔️ 成功
push 特殊 默认 成功
pop 默认 特殊 ✔️ 成功
pop 特殊 默认 ✔️ 成功
pop 特殊 默认 成功
push 特殊1 特殊2 ✔️ 成功
pop 特殊1 默认2 ✔️ 成功
  • 特殊的都是可旋转的,因此这种状况剔除了
6.present和dismiss的状况
动做 当前 目标 目标可旋转 结果
present 默认 特殊 ✔️ 奔溃(1)
present 特殊 默认 ✔️ 成功
present 特殊 默认 成功
dismiss 默认 特殊 ✔️ 成功
dismiss 特殊 默认 ✔️ 成功
dismiss 特殊 默认 成功
present 特殊1 特殊2 ✔️ 成功
dismiss 特殊1 默认2 ✔️ 成功

奔溃1的问题是由于没有实现preferredInterfaceOrientationForPresentation,而默认结果是当前的statusBar的样式,从默认过去,那就是竖直方向,而这个界面supportedInterfaceOrientations的样式又是横屏,因此优先的方向(preferredxxx)不包含在支持的方向(supportedxxx)里就奔溃了。按照以前的约定,supportedInterfaceOrientations是必须实现的,实现了就成功了。

因此解决方案经过测试。

最后,present和push的切换方式有个不一样:若是A--->B使用present方式,A不可旋转,但同时支持横竖屏,B可旋转,支持横竖屏,那么 A竖屏 ---> B竖屏 ---> 旋转到横屏 ---> dismiss 这个流程后,A会变成横屏且不可旋转。

也就是dismiss时,返回的界面不看你能不能旋转,若是你支持当前的方向,就会直接变成当前方向的样式。而supportedInterfaceOrientations默认是3个方向的,因此不实现这个方法而使用默认的,在dismiss的时候会有坑。

相关文章
相关标签/搜索