1.導航控制器棧內(nèi)部的VC方向是導航控制器來決定的瓜客。nav --- A --- B --- C,C的旋轉方法是不起作用的殊霞,靠的是nav的-(BOOL)shouldAutorotate
和-(UIInterfaceOrientationMask)supportedInterfaceOrientations
粉私。
解決方案是:重寫nav的旋轉方法绷旗,把結果指向到topViewController:
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return self.topViewController.supportedInterfaceOrientations;
}
對于UITabBarController
,就轉嫁為它的selectedViewController
的結果肘习。
2.旋轉的邏輯流是:手機方向改變了 ---> 通知APP ---> 調用APP內(nèi)部的關鍵VC(TabBar或Nav)的旋轉方法 ---> 得到可旋轉并且支持當前設備方向 ---> 旋轉到指定方向际乘。
邏輯流的初始時物理上手機方向改變了。所有如果A push到 B井厌,A只支持豎屏蚓庭,而B只支持橫屏,如果這時手機物理方向沒變仅仆,那么B還是會跟A一樣豎屏,哪怕它只支持橫屏并且問題1也解決了的垢袱。
解決方案:強制旋轉墓拜。
@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:
這個私有方法來實現(xiàn)请契。
3.有了問題1和2的解決咳榜,對于整個項目的基本方案確定。一般項目會有一個主方向爽锥,絕大多數(shù)界面都是這個方向涌韩,比如豎屏,然后有特定界面是特定方向氯夷。
那么解決方案是:
- 在target --> General --> Development Info里配置支持所有可能的方向
- 使用baseViewController臣樱,項目所有VC都繼承與它,在baseVC里寫入默認方向設置腮考,這個默認設置就是絕大多數(shù)界面支持的方向雇毫。
- 然后在特殊方向界面,重寫
-(BOOL)shouldAutorotate
和-(UIInterfaceOrientationMask)supportedInterfaceOrientations
來達到自己的目的踩蔚。 - 特殊界面因為要強制旋轉棚放,所以在進入界面是旋轉到需要方向:
-(void)viewWillAppear:(BOOL)animated{
[UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationLandscapeLeft)];
}
4.push和pop的結果測試:
要驗證問題3的方案是否滿足需要,滿足需要的意思是:每個頁面能夠顯示它支持的方向而且不會干擾到其他界面馅闽。所以測試一下push和pop的情況飘蚯。
測試變量有:
- 當前的界面是默認還是特殊馍迄,這里把只有豎屏設為默認绞佩,橫屏為特殊情況应结。默認代表這個VC只需繼承baseVC的方向相關方法攘轩,不做任何額外處理厚脉。
- 界面是push還是pop
- 下一個界面是默認情況還是特殊情況蓄愁。
- 下一個界面的shouldAutorate是否為YES暮胧。
前后兩個界面方向一致的畫好唯,結果肯定是好的鞋诗,就不測試 了穴店。最終測試結果如下:
動作 | 當前 | 目標 | 目標可旋轉 | 結果 |
---|---|---|---|---|
push | 默認 | 特殊 | ?? | 成功 |
push | 默認 | 特殊 | ? | 失敗 |
push | 特殊 | 默認 | ?? | 失敗(2) |
push | 特殊 | 默認 | ? | 失敗(3) |
pop | 默認 | 特殊 | ?? | 成功 |
pop | 默認 | 特殊 | ? | 失敗 |
pop | 特殊 | 默認 | ?? | 成功(1) |
pop | 特殊 | 默認 | ? | 成功(1) |
- 成功代表目標界面旋轉到了期望的方向
- 標記1:沒有旋轉撕捍,直接顯示的默認樣式(豎屏)。
- 標記2:從橫屏到豎屏泣洞,沒有切換方向忧风,為什么?因為UIDevice的方向沒有修改球凰,沒有觸發(fā)切換效果狮腿。所以在特殊界面離開的時候還要調用強制旋轉。其實只要相鄰的方向不同呕诉,就要在切換時觸發(fā)強制旋轉缘厢。
- 添加了
viewWillDisappear
里的強制旋轉后,標記2可以解決甩挫。但標記3還是失敗贴硫,其實push時,下一個界面如果是不可旋轉的伊者,那么方向一定是不變了英遭。
特殊界面只要保持,進入和離開時都調用強制旋轉亦渗,并且自身shouldAutorate
為YES,那么push或pop進入特殊界面都沒有問題挖诸。關鍵是從特殊界面離開進入默認界面,pop時是成功的法精,push時如果默認界面是不可旋轉的多律,就會失敗。
針對這個有兩種方案:
- 在離開前把當前界面旋轉為默認亿虽,先旋轉菱涤,再push。
- 把默認界面改為可旋轉洛勉。
5.特殊方向界面離開前先旋轉到默認
因為特殊界面支持的方向不包含默認方向粘秆,所以只是強制旋轉時不起作用的,在強制旋轉前還要修改支持的方向收毫。具體代碼:
- (IBAction)push:(id)sender {
[self changeOrientationBeforeDisappear]; //離開前先修改方向攻走,其他每個出口都要調用這個方法殷勘。不能在`viewWillDisappear`里調用,因為這時push等已經(jīng)觸發(fā)了
TFThirdViewController *thirdVC = [[TFThirdViewController alloc] init];
[self.navigationController pushViewController:thirdVC animated:YES];
}
-(void)changeOrientationBeforeDisappear{
_orientation = UIInterfaceOrientationMaskPortrait; //替換為默認方向
[UIDevice changeInterfaceOrientationTo:(UIInterfaceOrientationPortrait)];
_orientation = UIInterfaceOrientationMaskLandscapeLeft; //替換為特殊方向界面自身需要的方向
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return _orientation; //根據(jù)變量變化而變化
}
如果下一個界面不是默認昔搂,會是什么情況玲销?會有兩次旋轉。離開時旋轉到默認摘符,進入下一個界面贤斜,它自身又旋轉到指定方向。效果不好逛裤,如果想一次到位瘩绒,怎么辦?就要離開的時候知道下一個界面期望的方向是什么带族,然后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等已經(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ù)情況建一個baseVC,里面設置默認方向择克。
- 對特殊方向界面:
- 進入時(
viewWillAppear
)強制旋轉到需要的方向 - 離開時恬总,注意并不是
viewWillDisappear
,而是push操作之前,先修改方向為下一個界面的期望方向祠饺。 - 當然自身的
shouldAutorotate
保持為YES越驻。
- 進入時(
- 方向相關的3個方法全部要實現(xiàn)。因為基類(BaseVC)做了處理道偷,可以省去絕大部分的工作。特殊方向的界面單個處理即可记劈。
-
preferredInterfaceOrientationForPresentation
的方向要和進入時的方向一致勺鸦,這樣就不會有2次旋轉。
相比把基類的shouldAutorotate
改為YES,這個方案的好處是目木,把特殊情況的處理基本都壓縮在特殊界面自身內(nèi)部了换途,依賴的只有其他界面的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的問題是因為沒有實現(xiàn)preferredInterfaceOrientationForPresentation
,而默認結果是當前的statusBar的樣式懈息,從默認過去,那就是豎直方向摹恰,而這個界面supportedInterfaceOrientations
的樣式又是橫屏辫继,所以優(yōu)先的方向(preferredxxx)不包含在支持的方向(supportedxxx)里就奔潰了怒见。按照之前的約定,supportedInterfaceOrientations
是必須實現(xiàn)的姑宽,實現(xiàn)了就成功了遣耍。
所以解決方案通過測試。
最后炮车,present和push的切換方式有個不同:如果A--->B使用present方式舵变,A不可旋轉,但同時支持橫豎屏瘦穆,B可旋轉纪隙,支持橫豎屏,那么 A豎屏 ---> B豎屏 ---> 旋轉到橫屏 ---> dismiss 這個流程后难审,A會變成橫屏且不可旋轉瘫拣。
也就是dismiss時,返回的界面不看你能不能旋轉告喊,如果你支持當前的方向麸拄,就會直接變成當前方向的樣式。而supportedInterfaceOrientations
默認是3個方向的黔姜,所以不實現(xiàn)這個方法而使用默認的拢切,在dismiss的時候會有坑。