iOS屏幕旋轉(zhuǎn)解決方案

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。
  • 方向相關(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)槠渌缑婵赡懿幌MD(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ì)有坑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容