1.導(dǎo)航控制器棧內(nèi)部的VC方向是導(dǎo)航控制器來決定的。nav --- A --- B --- C,C的旋轉(zhuǎn)方法是不起作用的,靠的是nav的-(BOOL)shouldAutorotate和-(UIInterfaceOrientationMask)supportedInterfaceOrientations。
解決方案是:重寫nav的旋轉(zhuǎn)方法,把結(jié)果指向到topViewController:
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return self.topViewController.supportedInterfaceOrientations;
}
對(duì)于UITabBarController,就轉(zhuǎn)嫁為它的selectedViewController的結(jié)果。
2.旋轉(zhuǎn)的邏輯流是:手機(jī)方向改變了 ---> 通知APP ---> 調(diào)用APP內(nèi)部的關(guān)鍵VC(TabBar或Nav)的旋轉(zhuǎn)方法 ---> 得到可旋轉(zhuǎn)并且支持當(dāng)前設(shè)備方向 ---> 旋轉(zhuǎn)到指定方向。
邏輯流的初始時(shí)物理上手機(jī)方向改變了。所有如果A push到 B,A只支持豎屏,而B只支持橫屏,如果這時(shí)手機(jī)物理方向沒變,那么B還是會(huì)跟A一樣豎屏,哪怕它只支持橫屏并且問題1也解決了的。
解決方案:強(qiáng)制旋轉(zhuǎn)。
@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提供一個(gè)category,調(diào)用setOrientation:這個(gè)私有方法來實(shí)現(xiàn)。
3.有了問題1和2的解決,對(duì)于整個(gè)項(xiàng)目的基本方案確定。一般項(xiàng)目會(huì)有一個(gè)主方向,絕大多數(shù)界面都是這個(gè)方向,比如豎屏,然后有特定界面是特定方向。
那么解決方案是:
- 在target --> General --> Development Info里配置支持所有可能的方向
- 使用baseViewController,項(xiàng)目所有VC都繼承與它,在baseVC里寫入默認(rèn)方向設(shè)置,這個(gè)默認(rèn)設(shè)置就是絕大多數(shù)界面支持的方向。
- 然后在特殊方向界面,重寫
-(BOOL)shouldAutorotate和-(UIInterfaceOrientationMask)supportedInterfaceOrientations來達(dá)到自己的目的。 - 特殊界面因?yàn)橐獜?qiáng)制旋轉(zhuǎn),所以在進(jìn)入界面是旋轉(zhuǎn)到需要方向:
-(void)viewWillAppear:(BOOL)animated{
[UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationLandscapeLeft)];
}
4.push和pop的結(jié)果測(cè)試:
要驗(yàn)證問題3的方案是否滿足需要,滿足需要的意思是:每個(gè)頁(yè)面能夠顯示它支持的方向而且不會(huì)干擾到其他界面。所以測(cè)試一下push和pop的情況。
測(cè)試變量有:
- 當(dāng)前的界面是默認(rèn)還是特殊,這里把只有豎屏設(shè)為默認(rèn),橫屏為特殊情況。默認(rèn)代表這個(gè)VC只需繼承baseVC的方向相關(guān)方法,不做任何額外處理。
- 界面是push還是pop
- 下一個(gè)界面是默認(rèn)情況還是特殊情況。
- 下一個(gè)界面的shouldAutorate是否為YES。
前后兩個(gè)界面方向一致的畫,結(jié)果肯定是好的,就不測(cè)試 了。最終測(cè)試結(jié)果如下:
| 動(dòng)作 | 當(dāng)前 | 目標(biāo) | 目標(biāo)可旋轉(zhuǎn) | 結(jié)果 |
|---|---|---|---|---|
| push | 默認(rèn) | 特殊 | ?? | 成功 |
| push | 默認(rèn) | 特殊 | ? | 失敗 |
| push | 特殊 | 默認(rèn) | ?? | 失敗(2) |
| push | 特殊 | 默認(rèn) | ? | 失敗(3) |
| pop | 默認(rèn) | 特殊 | ?? | 成功 |
| pop | 默認(rèn) | 特殊 | ? | 失敗 |
| pop | 特殊 | 默認(rèn) | ?? | 成功(1) |
| pop | 特殊 | 默認(rèn) | ? | 成功(1) |
- 成功代表目標(biāo)界面旋轉(zhuǎn)到了期望的方向
- 標(biāo)記1:沒有旋轉(zhuǎn),直接顯示的默認(rèn)樣式(豎屏)。
- 標(biāo)記2:從橫屏到豎屏,沒有切換方向,為什么?因?yàn)閁IDevice的方向沒有修改,沒有觸發(fā)切換效果。所以在特殊界面離開的時(shí)候還要調(diào)用強(qiáng)制旋轉(zhuǎn)。其實(shí)只要相鄰的方向不同,就要在切換時(shí)觸發(fā)強(qiáng)制旋轉(zhuǎn)。
- 添加了
viewWillDisappear里的強(qiáng)制旋轉(zhuǎn)后,標(biāo)記2可以解決。但標(biāo)記3還是失敗,其實(shí)push時(shí),下一個(gè)界面如果是不可旋轉(zhuǎn)的,那么方向一定是不變了。
特殊界面只要保持,進(jìn)入和離開時(shí)都調(diào)用強(qiáng)制旋轉(zhuǎn),并且自身shouldAutorate為YES,那么push或pop進(jìn)入特殊界面都沒有問題。關(guān)鍵是從特殊界面離開進(jìn)入默認(rèn)界面,pop時(shí)是成功的,push時(shí)如果默認(rèn)界面是不可旋轉(zhuǎn)的,就會(huì)失敗。
針對(duì)這個(gè)有兩種方案:
- 在離開前把當(dāng)前界面旋轉(zhuǎn)為默認(rèn),先旋轉(zhuǎn),再push。
- 把默認(rèn)界面改為可旋轉(zhuǎn)。
5.特殊方向界面離開前先旋轉(zhuǎn)到默認(rèn)
因?yàn)樘厥饨缑嬷С值姆较虿话J(rèn)方向,所以只是強(qiáng)制旋轉(zhuǎn)時(shí)不起作用的,在強(qiáng)制旋轉(zhuǎn)前還要修改支持的方向。具體代碼:
- (IBAction)push:(id)sender {
[self changeOrientationBeforeDisappear]; //離開前先修改方向,其他每個(gè)出口都要調(diào)用這個(gè)方法。不能在`viewWillDisappear`里調(diào)用,因?yàn)檫@時(shí)push等已經(jīng)觸發(fā)了
TFThirdViewController *thirdVC = [[TFThirdViewController alloc] init];
[self.navigationController pushViewController:thirdVC animated:YES];
}
-(void)changeOrientationBeforeDisappear{
_orientation = UIInterfaceOrientationMaskPortrait; //替換為默認(rèn)方向
[UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationPortrait)];
_orientation = UIInterfaceOrientationMaskLandscapeLeft; //替換為特殊方向界面自身需要的方向
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return _orientation; //根據(jù)變量變化而變化
}
如果下一個(gè)界面不是默認(rèn),會(huì)是什么情況?會(huì)有兩次旋轉(zhuǎn)。離開時(shí)旋轉(zhuǎn)到默認(rèn),進(jìn)入下一個(gè)界面,它自身又旋轉(zhuǎn)到指定方向。效果不好,如果想一次到位,怎么辦?就要離開的時(shí)候知道下一個(gè)界面期望的方向是什么,然后preferredInterfaceOrientationForPresentation正好符合這個(gè)意圖。
所以修改為:
@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]; //離開前先修改方向,其他每個(gè)出口都要調(diào)用這個(gè)方法。不能在`viewWillDisappear`里調(diào)用,因?yàn)檫@時(shí)push等已經(jīng)觸發(fā)了
[self.navigationController pushViewController:thirdVC animated:YES];
}
-(void)changeOrientationBeforeDisappearTo:(UIViewController *)nextVC{
_orientation = UIInterfaceOrientationMaskAll; //改為任意方向
[UIDevice changeInterfaceOrientationTo:[nextVC preferredInterfaceOrientationForPresentation]];
_orientation = _needOrientation; //替換為特殊方向界面自身需要的方向
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return _orientation; //根據(jù)變量變化而變化
}
@end
_needOrientation時(shí)當(dāng)前頁(yè)面需要的樣式。
總結(jié)起來就是:
- 給絕大多數(shù)情況建一個(gè)baseVC,里面設(shè)置默認(rèn)方向。
- 對(duì)特殊方向界面:
- 進(jìn)入時(shí)(
viewWillAppear)強(qiáng)制旋轉(zhuǎn)到需要的方向 - 離開時(shí),注意并不是
viewWillDisappear,而是push操作之前,先修改方向?yàn)橄乱粋€(gè)界面的期望方向。 - 當(dāng)然自身的
shouldAutorotate保持為YES。
- 進(jìn)入時(shí)(
- 方向相關(guān)的3個(gè)方法全部要實(shí)現(xiàn)。因?yàn)榛?BaseVC)做了處理,可以省去絕大部分的工作。特殊方向的界面單個(gè)處理即可。
-
preferredInterfaceOrientationForPresentation的方向要和進(jìn)入時(shí)的方向一致,這樣就不會(huì)有2次旋轉(zhuǎn)。
相比把基類的shouldAutorotate改為YES,這個(gè)方案的好處是,把特殊情況的處理基本都?jí)嚎s在特殊界面自身內(nèi)部了,依賴的只有其他界面的supportedInterfaceOrientations,這個(gè)方法是一個(gè)補(bǔ)充性的,不會(huì)干擾其他界面原本的設(shè)計(jì)。而對(duì)shouldAutorotate卻比較麻煩,因?yàn)槠渌缑婵赡懿幌MD(zhuǎn)。
再次測(cè)試pop和push情況:
| 動(dòng)作 | 當(dāng)前 | 目標(biāo) | 目標(biāo)可旋轉(zhuǎn) | 結(jié)果 |
|---|---|---|---|---|
| push | 默認(rèn) | 特殊 | ?? | 成功 |
| push | 特殊 | 默認(rèn) | ?? | 成功 |
| push | 特殊 | 默認(rèn) | ? | 成功 |
| pop | 默認(rèn) | 特殊 | ?? | 成功 |
| pop | 特殊 | 默認(rèn) | ?? | 成功 |
| pop | 特殊 | 默認(rèn) | ? | 成功 |
| push | 特殊1 | 特殊2 | ?? | 成功 |
| pop | 特殊1 | 默認(rèn)2 | ?? | 成功 |
- 特殊的都是可旋轉(zhuǎn)的,所以這種情況剔除了
6.present和dismiss的情況
| 動(dòng)作 | 當(dāng)前 | 目標(biāo) | 目標(biāo)可旋轉(zhuǎn) | 結(jié)果 |
|---|---|---|---|---|
| present | 默認(rèn) | 特殊 | ?? | 奔潰(1) |
| present | 特殊 | 默認(rèn) | ?? | 成功 |
| present | 特殊 | 默認(rèn) | ? | 成功 |
| dismiss | 默認(rèn) | 特殊 | ?? | 成功 |
| dismiss | 特殊 | 默認(rèn) | ?? | 成功 |
| dismiss | 特殊 | 默認(rèn) | ? | 成功 |
| present | 特殊1 | 特殊2 | ?? | 成功 |
| dismiss | 特殊1 | 默認(rèn)2 | ?? | 成功 |
奔潰1的問題是因?yàn)闆]有實(shí)現(xiàn)preferredInterfaceOrientationForPresentation,而默認(rèn)結(jié)果是當(dāng)前的statusBar的樣式,從默認(rèn)過去,那就是豎直方向,而這個(gè)界面supportedInterfaceOrientations的樣式又是橫屏,所以優(yōu)先的方向(preferredxxx)不包含在支持的方向(supportedxxx)里就奔潰了。按照之前的約定,supportedInterfaceOrientations是必須實(shí)現(xiàn)的,實(shí)現(xiàn)了就成功了。
所以解決方案通過測(cè)試。
最后,present和push的切換方式有個(gè)不同:如果A--->B使用present方式,A不可旋轉(zhuǎn),但同時(shí)支持橫豎屏,B可旋轉(zhuǎn),支持橫豎屏,那么 A豎屏 ---> B豎屏 ---> 旋轉(zhuǎn)到橫屏 ---> dismiss 這個(gè)流程后,A會(huì)變成橫屏且不可旋轉(zhuǎn)。
也就是dismiss時(shí),返回的界面不看你能不能旋轉(zhuǎn),如果你支持當(dāng)前的方向,就會(huì)直接變成當(dāng)前方向的樣式。而supportedInterfaceOrientations默認(rèn)是3個(gè)方向的,所以不實(shí)現(xiàn)這個(gè)方法而使用默認(rèn)的,在dismiss的時(shí)候會(huì)有坑。