-(BOOL)shouldAutorotate
和-(UIInterfaceOrientationMask)supportedInterfaceOrientations
。解决方案是:重写nav的旋转方法,把结果指向到topViewController:bash
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return self.topViewController.supportedInterfaceOrientations;
}
复制代码
对于UITabBarController
,就转嫁为它的selectedViewController
的结果。app
逻辑流的初始时物理上手机方向改变了。全部若是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
那么解决方案是:设计
-(BOOL)shouldAutorotate
和-(UIInterfaceOrientationMask)supportedInterfaceOrientations
来达到本身的目的。-(void)viewWillAppear:(BOOL)animated{
[UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationLandscapeLeft)];
}
复制代码
要验证问题3的方案是否知足须要,知足须要的意思是:每一个页面可以显示它支持的方向并且不会干扰到其余界面。因此测试一下push和pop的状况。code
测试变量有:继承
先后两个界面方向一致的画,结果确定是好的,就不测试 了。最终测试结果以下:get
动做 | 当前 | 目标 | 目标可旋转 | 结果 |
---|---|---|---|---|
push | 默认 | 特殊 | ✔️ | 成功 |
push | 默认 | 特殊 | ❌ | 失败 |
push | 特殊 | 默认 | ✔️ | 失败(2) |
push | 特殊 | 默认 | ❌ | 失败(3) |
pop | 默认 | 特殊 | ✔️ | 成功 |
pop | 默认 | 特殊 | ❌ | 失败 |
pop | 特殊 | 默认 | ✔️ | 成功(1) |
pop | 特殊 | 默认 | ❌ | 成功(1) |
viewWillDisappear
里的强制旋转后,标记2能够解决。但标记3仍是失败,其实push时,下一个界面若是是不可旋转的,那么方向必定是不变了。特殊界面只要保持,进入和离开时都调用强制旋转,而且自身shouldAutorate
为YES,那么push或pop进入特殊界面都没有问题。关键是从特殊界面离开进入默认界面,pop时是成功的,push时若是默认界面是不可旋转的,就会失败。string
针对这个有两种方案:
由于特殊界面支持的方向不包含默认方向,因此只是强制旋转时不起做用的,在强制旋转前还要修改支持的方向。具体代码:
- (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
时当前页面须要的样式。
总结起来就是:
viewWillAppear
)强制旋转到须要的方向viewWillDisappear
,而是push操做以前,先修改方向为下一个界面的指望方向。shouldAutorotate
保持为YES。preferredInterfaceOrientationForPresentation
的方向要和进入时的方向一致,这样就不会有2次旋转。相比把基类的shouldAutorotate
改成YES,这个方案的好处是,把特殊状况的处理基本都压缩在特殊界面自身内部了,依赖的只有其余界面的supportedInterfaceOrientations
,这个方法是一个补充性的,不会干扰其余界面本来的设计。而对shouldAutorotate
却比较麻烦,由于其余界面可能不但愿旋转。
再次测试pop和push状况:
动做 | 当前 | 目标 | 目标可旋转 | 结果 |
---|---|---|---|---|
push | 默认 | 特殊 | ✔️ | 成功 |
push | 特殊 | 默认 | ✔️ | 成功 |
push | 特殊 | 默认 | ❌ | 成功 |
pop | 默认 | 特殊 | ✔️ | 成功 |
pop | 特殊 | 默认 | ✔️ | 成功 |
pop | 特殊 | 默认 | ❌ | 成功 |
push | 特殊1 | 特殊2 | ✔️ | 成功 |
pop | 特殊1 | 默认2 | ✔️ | 成功 |
动做 | 当前 | 目标 | 目标可旋转 | 结果 |
---|---|---|---|---|
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的时候会有坑。