謹慎能捕千秋蟬,當心駛得萬年船 ——《莊子語錄》git
方向旋轉在平常App上基本都會用到,用的時候可能會由於趕工期而以實現功能爲主,認真思考了爲啥去這樣作有沒有bug或者後續的開發中是否還會用到。不僅是屏幕旋轉,還有其餘的東西也是這樣,致使以前作的時候感受也會了,實際開發的時候,有時就會像喝斷片同樣。github
此文的目的是爲了加深理解和鞏固一下知識記憶,另外此文也附帶了對屏幕旋轉的一些理解和一些須要注意的地方,當心駛得萬年船哇。objective-c
UIDeviceOrientation:是描述設備的方向,含有如下值:api
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait, // 豎立
UIDeviceOrientationPortraitUpsideDown, // 倒豎
UIDeviceOrientationLandscapeLeft, // 橫屏,home鍵在右邊
UIDeviceOrientationLandscapeRight, // 橫屏,home鍵在左邊
UIDeviceOrientationFaceUp, // 屏幕朝上
UIDeviceOrientationFaceDown // 屏幕朝下
其中UIDeviceOrientationLandscapeLeft能夠這樣理解:以home鍵或Home Indicator(下文僅用home鍵形容同等)爲參照物,UIDeviceOrientationLandscapeRight就是設備向右旋轉了,因此home鍵在左側。反之亦然。
複製代碼
UIInterfaceOrientation:描述的是頁面的方向,含有如下值:bash
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
其中之因此UIInterfaceOrientationLandscapeLeft和UIDeviceOrientationLandscapeRight等價,就是由於設備旋轉後,頁面須要向反方向旋轉才能符合正常使用的規範。
複製代碼
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
能夠經過獲取設備的orientation屬性值來判斷當前設備的方向。函數
有些時候須要經過監聽來獲取設備的方向,須要經過使用通知來監聽,可是有個注意的地方就是從設備方向改變的通知中獲取數據先後須要調用一對方法:beginGeneratingDeviceOrientationNotifications
和endGeneratingDeviceOrientationNotifications
動畫
// 經過通知名:UIDeviceOrientationDidChangeNotification 來獲取
// 調用生成設備方向變化的通知方法
[[NSNotificationCenter defaultCenter] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeOrientation) name:UIDeviceOrientationDidChangeNotification object:nil];
// ...
// 移除通知前需調用結束生成設備方向變化的通知
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] removeObserver:self];
複製代碼
有點須要很是注意的是:若是用戶在控制欄鎖定了屏幕方向,收不到方向改變的通知,包括即便你所在的試圖控制器支持多個方向,也只會強轉第一個方向。ui
還有一點就是狀態欄的問題,若是沒有設置狀態的樣式,在強轉橫屏時將會隱藏狀態欄。
另外使用此種方式時可能會收到狀態UIDeviceOrientationUnknown
,好比第一次進入app的過程當中以及只支持豎屏的狀況時,可是通常跳轉時會有幾回的方向回調的,當出現這個狀態時能夠返回豎屏的狀態。
咱們已知大部分頁面都是豎屏的,因此咱們勾選支持的方向應只勾選 Portrait。
在AppDelegate中的設置能夠不進行設置或者只返回UIInterfaceOrientationMaskPortrait,將會以默認的豎屏展現。
咱們在須要強制豎屏或橫屏的頁面進行重寫shouldAutorotate
和supportedInterfaceOrientations
,且前者的值應爲YES,不然可能會出現沒法強轉的問題。
下面是強制設備向左旋轉後橫屏的代碼:
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];
複製代碼
從上述的設備旋轉方式可知,若是頁面在UINavigationController或UITabBarController裏面,須要給導航欄或底部欄寫個分類或者在自定義類裏重寫兩個方法:shouldAutorotate
和supportedInterfaceOrientations
。值需當前視圖控制器的值同樣,能夠經過self.visibleViewController
獲取當前顯示的試圖控制器,進一步獲取所需值。
若是強制橫屏或者豎屏時還支持自動旋轉屏幕,就須要確認手機的方向鎖是否打開,若是打開將沒法旋轉至可支持的方向。
好須要作返回以前頁面的設置,不然可能出現push一個新的頁面強轉橫屏,pop回去的時候發現以前的頁面也變成了橫屏。
最後一點就是處理以上的問題以後,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的時候:
demo地址:github.com/oymuzi/OMRo…
該說的都說了,再哆嗦一句就是使用強轉時須要寫個轉場動畫,否則返回的時候很難看。狀態欄的設置直接調用api就行,其餘沒啥了。