一、監聽螢幕旋轉方向
在處理iOS橫豎屏時,經常會和UIDeviceOrientation、UIInterfaceOrientation和UIInterfaceOrientationMask這三個列舉型別打交道,它們從不同角度描述了螢幕旋轉方向。
1、UIDeviceOrientation:裝置方向
iOS的裝置方向是通過iOS的加速計來獲取的。
1)iOS定義了以下七種裝置方向
1 2 3 4 5 6 7 8 9 |
typedef NS_ENUM(NSInteger, UIDeviceOrientation) { UIDeviceOrientationUnknown, // 未知方向,可能是裝置(螢幕)斜置 UIDeviceOrientationPortrait, // 裝置(螢幕)直立 UIDeviceOrientationPortraitUpsideDown, // 裝置(螢幕)直立,上下顛倒 UIDeviceOrientationLandscapeLeft, // 裝置(螢幕)向左橫置 UIDeviceOrientationLandscapeRight, // 裝置(螢幕)向右橫置 UIDeviceOrientationFaceUp, // 裝置(螢幕)朝上平躺 UIDeviceOrientationFaceDown // 裝置(螢幕)朝下平躺 }; |
說明:UIDeviceOrientation參考home鍵方向,如:home方向在右,裝置(螢幕)方向向左(UIDeviceOrientationLandscapeLeft)
2)讀取裝置方向
UIDevice單例代表當前的裝置。從這個單例中可以獲得的資訊裝置,如裝置方向orientation。
1 |
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation; |
3)監聽、處理和移除 裝置方向改變的通知
當裝置方向變化時候,發出UIDeviceOrientationDidChangeNotification通知;註冊監聽該通知,可以針對不同的裝置方向處理檢視展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
//開啟和監聽 裝置旋轉的通知(不開啟的話,裝置方向一直是UIInterfaceOrientationUnknown) if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; } [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleDeviceOrientationChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; //裝置方向改變的處理 - (void)handleDeviceOrientationChange:(NSNotification *)notification{ UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation; switch (ddeviceOrientation) { case UIDeviceOrientationFaceUp: NSLog(@"螢幕朝上平躺"); break; case UIDeviceOrientationFaceDown: NSLog(@"螢幕朝下平躺"); break; case UIDeviceOrientationUnknown: NSLog(@"未知方向"); break; case UIDeviceOrientationLandscapeLeft: NSLog(@"螢幕向左橫置"); break; case UIDeviceOrientationLandscapeRight: NSLog(@"螢幕向右橫置"); break; case UIDeviceOrientationPortrait: NSLog(@"螢幕直立"); break; case UIDeviceOrientationPortraitUpsideDown: NSLog(@"螢幕直立,上下顛倒"); break; default: NSLog(@"無法辨識"); break; } } //最後在dealloc中移除通知 和結束裝置旋轉的通知 - (void)dealloc{ //... [[NSNotificationCenter defaultCenter]removeObserver:self]; [[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications]; |
說明:手機鎖定豎屏後,UIDeviceOrientationDidChangeNotification通知就失效了。
2、UIInterfaceOrientation:介面方向
介面方向是反應iOS中介面的方向,它和Home按鈕的方向是一致的。
1)iOS定義了以下五種介面方向
1 2 3 4 5 6 7 |
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, //未知方向 UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, //介面直立 UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, //介面直立,上下顛倒 UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, //介面朝左 UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft //介面朝右 } __TVOS_PROHIBITED; |
說明:從定義可知,介面方向和設別方向有對應關係,如介面的豎直方向就是 裝置的豎直方向:UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown
2)讀取介面方向
UIInterfaceOrientation和狀態列有關,通過UIApplication的單例呼叫statusBarOrientation來獲取
1 |
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; |
3)監聽、處理和移除 介面方向改變的通知
當介面方向變化時候,先後發出UIApplicationWillChangeStatusBarOrientationNotification和UIApplicationDidChangeStatusBarOrientationNotification通知;註冊監聽這兩個通知,可以針對不同的介面方向處理檢視展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//以監聽UIApplicationDidChangeStatusBarOrientationNotification通知為例 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleStatusBarOrientationChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; //介面方向改變的處理 - (void)handleStatusBarOrientationChange: (NSNotification *)notification{ UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; switch (interfaceOrientation) { case UIInterfaceOrientationUnknown: NSLog(@"未知方向"); break; case UIInterfaceOrientationPortrait: NSLog(@"介面直立"); break; case UIInterfaceOrientationPortraitUpsideDown: NSLog(@"介面直立,上下顛倒"); break; case UIInterfaceOrientationLandscapeLeft: NSLog(@"介面朝左"); break; case UIInterfaceOrientationLandscapeRight: NSLog(@"介面朝右"); break; default: break; } } //最後在dealloc中移除通知 - (void)dealloc{ //... [[NSNotificationCenter defaultCenter]removeObserver:self]; [[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications]; } |
說明:手機鎖定豎屏後,UIApplicationWillChangeStatusBarOrientationNotification和UIApplicationDidChangeStatusBarOrientationNotification通知也失效了。
3、UIInterfaceOrientationMask
UIInterfaceOrientationMask是為了整合多種UIInterfaceOrientation而定義的型別,和ViewController相關,一共有7種
1)iOS中的UIInterfaceOrientationMask定義
1 2 3 4 5 6 7 8 9 |
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), } __TVOS_PROHIBITED; |
2)UIInterfaceOrientationMask的使用
在ViewController可以重寫- (UIInterfaceOrientationMask)supportedInterfaceOrientations方法返回型別,來決定UIViewController可以支援哪些介面方向。
1 2 3 4 |
//支援介面直立 - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskPortrait; } |
總結:UIDeviceOrientation(裝置方向)和UIInterfaceOrientation(螢幕方向)是兩個不同的概念。前者代表了裝置的一種狀態,而後者是螢幕為了應對不同的裝置狀態,做出的使用者介面上的響應。在iOS裝置旋轉時,由UIKit接收到旋轉事件,然後通過AppDelegate通知當前程式的UIWindow物件,UIWindow物件通知它的rootViewController,如果該rootViewController支援旋轉後的螢幕方向,完成旋轉,否則不旋轉;彈出的ViewController也是如此處理。
二、檢視控制器中旋轉方向的設定
0、關於禁止橫屏的操作(不建議)
比較常規的方法有兩種。
方法1:在專案的General–>Deployment Info–>Device Orientation中,只勾選Portrait(豎屏)
勾選Portrait.png
方法2:Device Orientation預設設定,在Appdelegate中實現supportedInterfaceOrientationsForWindow:只返回UIInterfaceOrientationMaskPortraitt(豎屏)
1 2 3 |
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { return UIInterfaceOrientationMaskPortrait; } |
說明:極少的APP中所有介面都是豎屏的,因為總會有介面需要支援橫屏,如視訊播放頁。所以不建議設定禁止APP頁面橫屏。
下面介紹如何讓專案中的 檢視控制器中旋轉方向的設定
1、APP支援多個方向
APP支援多個方向.png
說明:如此,APP支援橫屏和豎屏了,但是具體檢視控制器支援的頁面方向還需要進一步處理。由於不支援豎屏顛倒(Upside Down),即使裝置上下顛倒,通過API也不會獲得裝置、螢幕上下顛倒方向的。
2、支援ViewController螢幕方向設定
1)關鍵函式
檢視控制器支援的介面方向主要由以下三個函式控制
1 2 3 4 5 6 7 8 |
//是否自動旋轉,返回YES可以自動旋轉,返回NO禁止旋轉 - (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED; //返回支援的方向 - (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED; //由模態推出的檢視控制器 優先支援的螢幕方向 - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED; |
2) QSBaseViewController設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//QSBaseViewController.h @interface QSBaseController : UIViewController @end //QSBaseViewController.m @implementation QSBaseController //#pragma mark - 控制螢幕旋轉方法 //是否自動旋轉,返回YES可以自動旋轉,返回NO禁止旋轉 - (BOOL)shouldAutorotate{ return NO; } //返回支援的方向 - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskPortrait; } //由模態推出的檢視控制器 優先支援的螢幕方向 - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ return UIInterfaceOrientationPortrait; } @end |
說明1:QSBaseViewController預設不支援旋轉,只支援 介面豎直方向,專案中的Controller都繼承自QSBaseViewController,可以通過重寫這三個方法來讓Controller支援除豎屏之外的方向或旋轉。
3) 在QSNavigationController設定
目標:通過QSNavigationController來push檢視控制器時,把支援螢幕旋轉的設定交給最新push進來([self.viewControllers lastObject])的viewController來設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//QSNavigationController.h @interface QSNavigationController : UINavigationController @end //QSNavigationController.m @implementation QSNavigationController #pragma mark - 控制螢幕旋轉方法 - (BOOL)shouldAutorotate{ return [[self.viewControllers lastObject]shouldAutorotate]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return [[self.viewControllers lastObject]supportedInterfaceOrientations]; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation]; } @end |
4) 在QSTabBarController設定
目標:TabBarController通常作為整個程式的rootViewController,UITabBar上面顯示的每一個Tab都對應著一個ViewController;每點選一個Tab,出現的ViewController(self.selectedViewController)對螢幕旋轉和支援方向的設定 交給其自身去控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//QSTabBarController.h @interface QSTabBarController : UITabBarController @end //QSTabBarController.m @implementation QSTabBarController #pragma mark - 控制螢幕旋轉方法 - (BOOL)shouldAutorotate{ return [self.selectedViewController shouldAutorotate]; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return [self.selectedViewController supportedInterfaceOrientations]; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ return [self.selectedViewController preferredInterfaceOrientationForPresentation]; } @end |
三、螢幕旋轉方向下的檢視處理
1、螢幕旋轉時,建議監聽UIApplicationDidChangeStatusBarOrientationNotification
原因1:supportedInterfaceOrientations方法中最終返回的是 多個介面方向。
原因2(最重要的原因):我們真正要處理的是頁面方向發生旋轉UI的變化。而在裝置的物理方向發生旋轉的時候,如果此時當前控制器的頁面並沒有旋轉,我們這時改變UI佈局,可能就發生問題了。
2、螢幕的寬高處理
1)在iOS 8之後,當螢幕旋轉的時候,[[UIScreen mainScreen] bounds]也發生了改變。如橫屏時候的螢幕寬度 其實是豎屏的時候螢幕的高度。
2)我們處理檢視佈局時候,如果使用到螢幕的寬高,不要直接使用SCREEN_HEIGHT和SCREEN_WIDTH,而使用SCREEN_MIN和SCREEN_MAX
1 2 3 4 5 |
#define SCREEN_HEIGHT CGRectGetHeight([[UIScreen mainScreen] bounds]) #define SCREEN_WIDTH CGRectGetWidth([[UIScreen mainScreen] bounds]) #define SCREEN_MIN MIN(SCREEN_HEIGHT,SCREEN_WIDTH) #define SCREEN_MAX MAX(SCREEN_HEIGHT,SCREEN_WIDTH) |
說明:豎屏時候,寬是SCREEN_MIN,高是SCREEN_MAX;橫屏時候,寬是SCREEN_MAX,高是SCREEN_MIN。
3、螢幕旋轉下處理Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//監聽UIApplicationDidChangeStatusBarOrientationNotification的處理 - (void)handleStatusBarOrientationChange: (NSNotification *)notification{ UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; BOOL isLandscape = NO; switch (interfaceOrientation) { case UIInterfaceOrientationUnknown: NSLog(@"未知方向"); break; case UIInterfaceOrientationPortrait: case UIInterfaceOrientationPortraitUpsideDown: isLandscape = NO; break; case UIInterfaceOrientationLandscapeLeft: case UIInterfaceOrientationLandscapeRight: isLandscape = YES; break; default: break; } if (isLandscape) { self.tableView.frame = CGRectMake(0, 0, SCREEN_MAX, SCREEN_MIN - 44); }else{ self.tableView.frame = CGRectMake(0, 0, SCREEN_MIN, SCREEN_MAX - 64); } [self.tableView reloadData]; } |
說明:當然也可以選擇使用Masonry這樣優秀的AutoLayout佈局第三方庫來處理,storyBoard來佈局次之。
4、螢幕旋轉下處理Demo效果圖
橫屏下效果.png
5、螢幕旋轉處理的建議
1)旋轉前後,view當前顯示的位置儘量不變
2)旋轉過程中,暫時介面操作的響應
3)檢視中有tableview的話,旋轉後,強制 [tableview reloadData],保證在方向變化以後,新的row能夠充滿全屏。
四、強制橫屏
APP中某些頁面,如視訊播放頁,一出現就要求橫屏。這些橫屏頁面或模態彈出、或push進來。
1、模態彈出ViewController情況下 強制橫屏的設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//QSShow3Controller.m - (BOOL)shouldAutorotate{ return NO; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskLandscapeRight; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ return UIInterfaceOrientationLandscapeRight; } //模態彈出 QSShow3Controller *vc = [[QSShow3Controller alloc]init]; [self presentViewController:vc animated:YES completion:nil]; |
說明:這種情況比較簡單處理。
2、push推入ViewController情況下 強制橫屏的設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//QSShow4Controller.m -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; [self setInterfaceOrientation:UIInterfaceOrientationLandscapeRight]; } //強制轉屏(這個方法最好放在BaseVController中) - (void)setInterfaceOrientation:(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]]; // 從2開始是因為前兩個引數已經被selector和target佔用 [invocation setArgument:&orientation atIndex:2]; [invocation invoke]; } } //必須返回YES - (BOOL)shouldAutorotate{ return YES; } - (UIInterfaceOrientationMask)supportedInterfaceOrientations{ return UIInterfaceOrientationMaskLandscapeRight; } //Push推入 QSShow4Controller *vc = [[QSShow4Controller alloc]init]; [self.navigationController pushViewController:vc animated:YES]; |
說明:蘋果不允許直接呼叫setOrientation方法,否則有被拒的風險;使用NSInvocation物件給[UIDevice currentDevice]發訊息,強制改變裝置方向,使其頁面方向對應改變,這是蘋果允許的。
五、其他
- 1、 APP啟動時,手機橫屏下,首頁UI(該頁面只支援豎屏)出錯(add by 2017/6/20)
12//設定設定狀態列豎屏[[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait]; - 以上詳細原始碼參考:QSRotationScreenDemo