iOS橫豎屏切換

基礎概念

UIDeviceOrientation

UIDeviceOrientation,表示設備朝向,可以通過[UIDevice currentDevice] orientation]獲取,取值有:

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,             // 未知,啟動時會出現
    UIDeviceOrientationPortrait,            // 豎屏,home鍵在底部
    UIDeviceOrientationPortraitUpsideDown,  // 倒立,home鍵在頂部
    UIDeviceOrientationLandscapeLeft,       // 左橫屏,home鍵在右邊
    UIDeviceOrientationLandscapeRight,      // 右橫屏,home鍵在左邊
    UIDeviceOrientationFaceUp,              // 屏幕朝上
    UIDeviceOrientationFaceDown             // 屏幕朝下
}

UIInterfaceOrientation

UIInterfaceOrientation,表示頁面內容朝向,注意UIInterfaceOrientation和UIDeviceOrientation的關系,其中UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,這是因為:
This is because rotating the device to the left requires rotating the content to the right.
不用特別細究兩者之間關系,我們只需要根據需要設置好UIInterfaceOrientation即可,通過
[UIApplication shareApplication] statusBarOrientation]可以獲取當前狀態欄朝向。

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
};

UIInterfaceOrientationMask

UIInterfaceOrientationMask,是由頁面內容朝向的二進制偏移組成,用來更方便描述某個界面支持的朝向。比如說下面的UIInterfaceOrientationMaskLandscape,其實就是由MaskLandscapeLeft和MaskLandscapeRight組成,這樣可以方便描述設備支持兩個橫屏方向。

typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
};

UIViewController相關

UIViewController關于橫豎屏的三個方法:

  1. shouldAutorotate,頁面是否允許自動旋轉,被棄用api:-shouldAutorotateToInterfaceOrientation的取代者;默認值為YES,表示當前界面允許跟隨設備旋轉而自動旋轉;
  2. supportedInterfaceOrientations,該界面支持的界面朝向,可以返回四個朝向的任意組合,iPad默認值是四個朝向都支持,iPhone默認值是除了UpsideDown的三個朝向。這個方法回調的前提是shouldAutorotate=YES。
  3. preferredInterfaceOrientationForPresentation,該界面被present出來的界面朝向,可以返回四個朝向的任意組合。如果沒有返回,則present時和原來界面保持一致。

AppDelegate相關

AppDelegate的supportedInterfaceOrientationsForWindow方法,根據需要返回當前window是否支持橫屏。

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window;

工程配置相關

在xcode的工程設置的General可以配置iPhone和iPad的頁面朝向支持。

橫豎屏切換實例

豎屏界面如何present橫屏界面

豎屏present橫屏是很普遍的場景,比如說視頻播放場景的全屏切換,就可以在當前豎屏的界面present一個橫屏播放界面的方式,實現橫豎屏切換。具體的操作步驟只需要兩步:

1,設置modalPresentationStyle為UIModalPresentationFullScreen;
2、preferredInterfaceOrientationForPresentation方法,返回UIInterfaceOrientationLandscapeRight;

比如說下面的LandscapeViewController界面:

// 點擊時設置
- (void)onClick {
    LandscapeViewController *landVC = [[LandscapeViewController alloc] init];
    landVC.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:landVC animated:YES completion:nil];
}

// LandscapeViewController內部代碼
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

思考??:

1、如果是橫屏轉豎屏呢?
2、如果想要自定義旋轉效果實現呢?(UIViewControllerAnimatedTransitioning協議)

豎屏界面如何push橫屏界面

比如說這樣的場景:App的rootVC是navigationVC,導航棧內先有一個豎屏界面,現在想要push一個橫屏界面LandscapeViewController。

一個簡單的方式如下:

// appdelegate實現
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if ([self.navigationVC.topViewController isKindOfClass:LandscapeViewController.class]) {
        return UIInterfaceOrientationMaskLandscapeRight;
    }
    else {
        return UIInterfaceOrientationMaskPortrait;
    }
}
// LandscapeViewController內部實現
- (void)viewDidLoad {
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:@selector(setOrientation:)]];
    invocation.selector = NSSelectorFromString(@"setOrientation:");
    invocation.target = [UIDevice currentDevice];
    int initOrientation = UIDeviceOrientationLandscapeRight;
    [invocation setArgument:&initOrientation atIndex:2];
    [invocation invoke];
}

思考??:

1、這里為什么沒有用到UIViewController的三個方法?
2、在viewDidLoad調用的旋轉方法是什么意思?

橫屏豎切換機制分析

前面的實例介紹了如何支持切換,但是也產生一些疑問:
工程配置文件也沒有設置橫屏,為什么后面就能支持橫屏?
工程配置、AppDelegate、UIViewController這三者,在橫豎屏切換過程的關系是什么?
自動旋轉和手動旋轉有什么區別?
....
僅僅知道切換適配代碼,是無法形成橫豎屏切換理解,也就很難回答上述的問題。
由于沒有找到解釋橫豎屏切換機制的官方文檔,以下根據自己的經驗對這個切換的機制進行分析。

系統如何知道App對界面朝向的支持

這里分兩種情況,App啟動前和App運行時。

App啟動前
在App啟動前進程還未加載,代碼無法運行,系統肯定無法通過AppDelegate或者UIViewController這種代碼的方式獲取橫豎屏的配置。所以在這種情況下,工程配置中的plist描述App對屏幕的適配,就可以很好幫助系統識別應該以什么樣的朝向啟動App。
所以在plist中增加橫屏的支持,好處是開屏能夠支持橫屏,這樣界面展示更加順滑;壞處也是開屏支持了橫屏,導致開屏為橫屏啟動的時候,UIScreen的mainScreen是橫屏的大小,但很多業務邏輯代碼都會以[UIScreen mainScreen]去取屏幕寬度和高度,所以很容易取到錯誤的值。

App運行時
當App進程加載完成,此時系統可以通過運行時詢問的方式,來動態獲取不同時機的界面朝向。
此時AppDelegate控制的是UIWindow層面的朝向,UIViewController控制的是VC層面的朝向。需要注意的是,當我們返回UIViewController的朝向時,還要考慮父容器的朝向。通常一個App的界面層級是UIWindow=>RootViewController(容器vc)=>UIViewController(界面vc)。假如只在UIWindow返回界面朝向也是允許的,就如同上面的實例分析中的push橫屏。

不同界面的朝向控制

還是假設UIWindow=>RootViewController(容器vc)=>UIViewController(界面vc)的層級,且當前ViewController是豎屏vc,現在需要push一個橫屏界面LandscapeViewController。
在每次界面切換的時候,系統都會回調確認新的界面朝向,最終結果為UIWindow朝向、容器vc朝向、界面vc朝向三者的“與”值。那么假如這個值沖突了呢?
假如supportedInterfaceOrientationsForWindow一直返回的豎屏,那么后面VC設置橫屏不會生效;
類似,假如UIWindow設置的是橫屏,那么后面VC設置豎屏也不會生效;
如果在界面切換的過程中發現返回的朝向值未確定,系統更傾向于保持當前朝向不變,并且可能會遇到以下的crash。

Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and [LandscapeViewController shouldAutorotate] is returning YES'

這個原則同樣適用于當返回多個結果,比如說當前界面是豎屏,然后UIWindow和ViewController的界面朝向都支持橫屏和豎屏都支持,那么此時會保持豎屏。

一種比較常用的設計:

// AppDelegate
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    return self.navigationVC.topViewController.supportedInterfaceOrientations;
}

// NavigationController
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return self.topViewController.supportedInterfaceOrientations;
}

// LandscapeViewController
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

自動旋轉和手動旋轉

自動旋轉指的是我們旋轉物理設備時,系統會觸發界面的旋轉。當我們從一個豎屏界面push一個橫屏界面時,即使橫屏界面設置了shouldAutorotate=YES,這個界面也不會變成橫屏,但是拿起來設備左右翻轉的時候,會發現隨著設備旋轉,界面也從橫屏變成了豎屏。這就是自動旋轉。
手動旋轉指的是手動觸發旋轉,根據經驗發現有兩個api,UIViewController的+attemptRotationToDeviceOrientation,還有UIDevice的setOrientation:可以調整界面朝向。前者是將界面朝向對齊設備朝向,是標準api;后者是調整設備朝向,是私有api。
假如我們在很多個豎屏界面中,需要強制橫屏某一個界面,如果是子界面可以使用present的方式,如果是push那么就必須要用到這個私有api。

注意事項

其他橫豎屏適配方式

1、視圖適配:通過transform修改layer從而在視圖上實現橫屏,但是此時屏幕寬度、狀態欄、安全距離等都保留豎屏狀態,這種方式僅僅適用于橫屏彈窗等部分場景;
2、新建Window:由于App的適配是UIWindow為單位,那么理論上是可以新建一個UIWindow來橫屏的界面;

橫豎屏切換通知

NSNotification通知

[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"NSNotification:%@, orientation:%d", note.userInfo, [(UIDevice *)note.object orientation]);
    }];

UIViewController回調

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator API_AVAILABLE(ios(8.0));
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容