經過優雅的方式強制旋轉屏幕

謹慎能捕千秋蟬,當心駛得萬年船                         ——《莊子語錄》git

前言

方向旋轉在平常App上基本都會用到,用的時候可能會由於趕工期而以實現功能爲主,認真思考了爲啥去這樣作有沒有bug或者後續的開發中是否還會用到。不僅是屏幕旋轉,還有其餘的東西也是這樣,致使以前作的時候感受也會了,實際開發的時候,有時就會像喝斷片同樣。github

此文的目的是爲了加深理解和鞏固一下知識記憶,另外此文也附帶了對屏幕旋轉的一些理解和一些須要注意的地方,當心駛得萬年船哇。objective-c

三個方向的枚舉

  1. UIDeviceOrientation:是描述設備的方向,含有如下值:api

    UIDeviceOrientationUnknown,
    UIDeviceOrientationPortrait,            // 豎立
    UIDeviceOrientationPortraitUpsideDown,  // 倒豎
    UIDeviceOrientationLandscapeLeft,       // 橫屏,home鍵在右邊
    UIDeviceOrientationLandscapeRight,      // 橫屏,home鍵在左邊
    UIDeviceOrientationFaceUp,              // 屏幕朝上
    UIDeviceOrientationFaceDown             // 屏幕朝下
      
    其中UIDeviceOrientationLandscapeLeft能夠這樣理解:以home鍵或Home Indicator(下文僅用home鍵形容同等)爲參照物,UIDeviceOrientationLandscapeRight就是設備向右旋轉了,因此home鍵在左側。反之亦然。
    複製代碼
  2. UIInterfaceOrientation:描述的是頁面的方向,含有如下值:bash

    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
    
    其中之因此UIInterfaceOrientationLandscapeLeft和UIDeviceOrientationLandscapeRight等價,就是由於設備旋轉後,頁面須要向反方向旋轉才能符合正常使用的規範。
    複製代碼
  3. UIInterfaceOrientationMask: 做用是在指定的視圖控制器中支持的頁面多種方向的集合,含有如下值:app

    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    複製代碼

第一個枚舉是用來描述設備的方向;第二個枚舉是用來描述頁面的方向;第三個枚舉則是用來描述頁面可支持的方向集合。另外設備面朝上和朝下兩種狀況一般不作考慮。ide

獲取設備的方向

  1. 能夠經過獲取設備的orientation屬性值來判斷當前設備的方向。函數

  2. 有些時候須要經過監聽來獲取設備的方向,須要經過使用通知來監聽,可是有個注意的地方就是從設備方向改變的通知中獲取數據先後須要調用一對方法:beginGeneratingDeviceOrientationNotificationsendGeneratingDeviceOrientationNotifications動畫

    // 經過通知名:UIDeviceOrientationDidChangeNotification 來獲取
    // 調用生成設備方向變化的通知方法
    [[NSNotificationCenter defaultCenter] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeOrientation) name:UIDeviceOrientationDidChangeNotification object:nil];
    
    // ...
    
    // 移除通知前需調用結束生成設備方向變化的通知
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    複製代碼
  3. 有點須要很是注意的是:若是用戶在控制欄鎖定了屏幕方向,收不到方向改變的通知,包括即便你所在的試圖控制器支持多個方向,也只會強轉第一個方向。ui

  4. 還有一點就是狀態欄的問題,若是沒有設置狀態的樣式,在強轉橫屏時將會隱藏狀態欄。

  5. 另外使用此種方式時可能會收到狀態UIDeviceOrientationUnknown,好比第一次進入app的過程當中以及只支持豎屏的狀況時,可是通常跳轉時會有幾回的方向回調的,當出現這個狀態時能夠返回豎屏的狀態。

設備旋轉的設置的幾種方式

  1. 由於設備支持的方向旋轉有幾種方式
    • 項目工程的設置:在工程的General和info.plist中設置都是同樣的,由於數據都存儲在info.plist中
    • AppDelegate中的設置:經過在代理中- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window設置支持的方向。
    • 單個視圖控制控制器的設置:須要其父控制器UITabBarController或UINavigationController也支持當前控制器的方向才行。
  2. 上述方式設置能夠簡單理解爲:項目工程的設置爲App默認設置;AppDelegate中的設置爲你當前設置的,因此會覆蓋項目工程的設置;而單個視圖控制器的設置爲當前頁面設置,可是受AppDelegate中的影響。
  3. 默認建立應用將會默認勾選除 Portrait Upside down這個選項外的方向。
  4. 即便你一個都不勾選,應用仍是會默認以 Portrait 的方式。

設備的旋轉的應用場景分析

場景分析
  1. 咱們在平常開發的過程當中或多或少會接觸到如下幾種狀況:
    • 個別頁面須要強制橫屏,其餘頁面只展現豎屏
    • 個別頁面須要強制橫屏,另個別頁面能夠自動根據設備旋轉,其餘頁面均爲豎屏。
    • 頁面都爲豎屏或者橫屏(這種不作探討)
  2. 從上述可知咱們須要支持個別頁面的強制方向或自動旋轉的需求。
強制橫屏的解決方案
  1. 咱們已知大部分頁面都是豎屏的,因此咱們勾選支持的方向應只勾選 Portrait。

  2. 在AppDelegate中的設置能夠不進行設置或者只返回UIInterfaceOrientationMaskPortrait,將會以默認的豎屏展現。

  3. 咱們在須要強制豎屏或橫屏的頁面進行重寫shouldAutorotatesupportedInterfaceOrientations,且前者的值應爲YES,不然可能會出現沒法強轉的問題。

  4. 下面是強制設備向左旋轉後橫屏的代碼:

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:@selector(setOrientation:)]];
    invocation.selector = NSSelectorFromString(@"setOrientation:");
    invocation.target = [UIDevice currentDevice];
    int initOrientation = UIDeviceOrientationLandscapeLeft; // 這裏咱們須要傳的值是設備方向值
    [invocation setArgument:&initOrientation atIndex:2];
    [invocation invoke];
    
    /** 下面兩行代碼是爲了當前導航欄或底部欄旋轉至設備方向*/
    [UINavigationController attemptRotationToDeviceOrientation];
    [UITabBarController attemptRotationToDeviceOrientation];
    複製代碼
  5. 從上述的設備旋轉方式可知,若是頁面在UINavigationControllerUITabBarController裏面,須要給導航欄或底部欄寫個分類或者在自定義類裏重寫兩個方法:shouldAutorotatesupportedInterfaceOrientations。值需當前視圖控制器的值同樣,能夠經過self.visibleViewController獲取當前顯示的試圖控制器,進一步獲取所需值。

  6. 若是強制橫屏或者豎屏時還支持自動旋轉屏幕,就須要確認手機的方向鎖是否打開,若是打開將沒法旋轉至可支持的方向。

  7. 好須要作返回以前頁面的設置,不然可能出現push一個新的頁面強轉橫屏,pop回去的時候發現以前的頁面也變成了橫屏。

  8. 最後一點就是處理以上的問題以後,A頁面是豎屏的,B頁面是橫屏的。A頁面pushB頁面後,在返回A頁面的時候動畫很難看,爲了更好的美觀度,寫一個轉場動畫來避免這個視覺上的bug。

解決方案剖析

綜上,發現若是有幾個頁面都須要設置,那麼單獨設置仍是很麻煩的,最好是能有一個函數或者一個屬性來配置是最好不過的,可是由於每一個頁面的設置都是不同的,狀態是須要存儲的,因此屬性好點。經過分類添加一個屬性,能夠設置支持的方向,有時只要強轉,即單獨的方向,而多個方向時,讓他可旋轉到可支持的方向,所以只需一個屬性便可,且此屬性能夠接受多個值,系統的的枚舉UIInterfaceOrientationMask的橫屏頁面方向和設備方向是相反的,爲了後者好區分,建立了一個位移枚舉,包括組合值。

@interface UIViewController (Rotate)

/** 支持的方向,只有一個方向時即爲強轉。當有多個參數時,將以 豎屏-左轉-右轉-倒立豎屏 順序來優先強轉第一個方向*/
@property (assign, nonatomic) OMRotateOrientation omSupportOrientations;

@end
複製代碼

從設備旋轉設置方式可知,爲了使頁面可以正常的旋轉,必須讓他的父控制器也支持當前頁面的方向,能夠經過分類簡單重寫:

#pragma mark- UINavigationController
@implementation UINavigationController (Rotate)

- (BOOL)shouldAutorotate{
    return self.visibleViewController.shouldAutorotate;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return self.visibleViewController.supportedInterfaceOrientations;
}

@end

#pragma mark- UITabBarController
@implementation UITabBarController (Rotate)

- (BOOL)shouldAutorotate{
    if ([self.selectedViewController isKindOfClass:[UINavigationController class]]) {
        return ((UINavigationController *)self.selectedViewController).visibleViewController.shouldAutorotate;
    }
    return self.selectedViewController.shouldAutorotate;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    if ([self.selectedViewController isKindOfClass:[UINavigationController class]]) {
        return ((UINavigationController *)self.selectedViewController).visibleViewController.supportedInterfaceOrientations;
    }
    return self.selectedViewController.supportedInterfaceOrientations;
}

@end
複製代碼

使用的時候只須要簡單的賦值便可:

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    self.omSupportOrientations = OMRotateOrientationLandscape;
}
複製代碼

使用場景中發現底部欄嵌套導航欄或其餘各類嵌套,發現都沒什麼大問題,不管是push仍是present,仍是tabbar的主頁面,仍是新跳導航欄之類的。只有一個bug就是隻有底部欄的時候,第一個視圖控制器若是寫在viewWillAppear中將會致使底部欄不可見,若是隻有底部的狀況,能夠在UITabBarControllerDelegate中的點擊方法中進行設置vc的支持方向並在底部欄的第一個頁面的viewDidLoad中進行設置可支持的方向,才能保證app進入的時候強轉至指定方向,不過貌似通常也不會在主頁面底部欄搞每一個頁面不一樣方向的吧。

2019-05-13更新補充:
感謝Simon小孩子發現的問題:一個橫屏時跳轉到豎向的新頁面時,新頁面並不會爲豎向,還是橫向的。解決方案:新push的頁面爲和當前頁面方向不一致時需在新頁面設置支持的方向(即便新頁面方向爲豎向)。

實現的效果

有底部欄和導航欄的時候

有tabBar和naviBar時

只有tabBar的時候:

只有tabBar

demo地址:github.com/oymuzi/OMRo…

總結

該說的都說了,再哆嗦一句就是使用強轉時須要寫個轉場動畫,否則返回的時候很難看。狀態欄的設置直接調用api就行,其餘沒啥了。

相關文章
相關標籤/搜索