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的時候會有坑。

相關文章
相關標籤/搜索