代碼處理iOS的橫豎屏旋轉(zhuǎn)
一、監(jiān)聽屏幕旋轉(zhuǎn)方向
在處理iOS橫豎屏?xí)r,經(jīng)常會和UIDeviceOrientation、UIInterfaceOrientation和UIInterfaceOrientationMask這三個(gè)枚舉類型打交道,它們從不同角度描述了屏幕旋轉(zhuǎn)方向。
1、UIDeviceOrientation:設(shè)備方向
iOS的設(shè)備方向是通過iOS的加速計(jì)來獲取的。
1)iOS定義了以下七種設(shè)備方向
- typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
- UIDeviceOrientationUnknown, // 未知方向,可能是設(shè)備(屏幕)斜置
- UIDeviceOrientationPortrait, // 設(shè)備(屏幕)直立
- UIDeviceOrientationPortraitUpsideDown, // 設(shè)備(屏幕)直立,上下顛倒
- UIDeviceOrientationLandscapeLeft, // 設(shè)備(屏幕)向左橫置
- UIDeviceOrientationLandscapeRight, // 設(shè)備(屏幕)向右橫置
- UIDeviceOrientationFaceUp, // 設(shè)備(屏幕)朝上平躺
- UIDeviceOrientationFaceDown // 設(shè)備(屏幕)朝下平躺
- };
說明:UIDeviceOrientation參考home鍵方向,如:home方向在右,設(shè)備(屏幕)方向向左(UIDeviceOrientationLandscapeLeft)
2)讀取設(shè)備方向
UIDevice單例代表當(dāng)前的設(shè)備。從這個(gè)單例中可以獲得的信息設(shè)備,如設(shè)備方向orientation。
- UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
3)監(jiān)聽、處理和移除 設(shè)備方向改變的通知
當(dāng)設(shè)備方向變化時(shí)候,發(fā)出UIDeviceOrientationDidChangeNotification通知;注冊監(jiān)聽該通知,可以針對不同的設(shè)備方向處理視圖展示。
- //開啟和監(jiān)聽 設(shè)備旋轉(zhuǎn)的通知(不開啟的話,設(shè)備方向一直是UIInterfaceOrientationUnknown)
- if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
- [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
- }
- [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleDeviceOrientationChange:)
- name:UIDeviceOrientationDidChangeNotification object:nil];
- //設(shè)備方向改變的處理
- - (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中移除通知 和結(jié)束設(shè)備旋轉(zhuǎn)的通知
- - (void)dealloc{
- //...
- [[NSNotificationCenter defaultCenter]removeObserver:self];
- [[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
說明:手機(jī)鎖定豎屏后,UIDeviceOrientationDidChangeNotification通知就失效了。
2、UIInterfaceOrientation:界面方向
界面方向是反應(yīng)iOS中界面的方向,它和Home按鈕的方向是一致的。
1)iOS定義了以下五種界面方向
- typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
- UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, //未知方向
- UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, //界面直立
- UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, //界面直立,上下顛倒
- UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, //界面朝左
- UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft //界面朝右
- } __TVOS_PROHIBITED;
說明:從定義可知,界面方向和設(shè)別方向有對應(yīng)關(guān)系,如界面的豎直方向就是 設(shè)備的豎直方向:UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown
2)讀取界面方向
UIInterfaceOrientation和狀態(tài)欄有關(guān),通過UIApplication的單例調(diào)用statusBarOrientation來獲取
- UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
3)監(jiān)聽、處理和移除 界面方向改變的通知
當(dāng)界面方向變化時(shí)候,先后發(fā)出UIApplicationWillChangeStatusBarOrientationNotification和UIApplicationDidChangeStatusBarOrientationNotification通知;注冊監(jiān)聽這兩個(gè)通知,可以針對不同的界面方向處理視圖展示。
- //以監(jiān)聽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];
- }
說明:手機(jī)鎖定豎屏后,UIApplicationWillChangeStatusBarOrientationNotification和UIApplicationDidChangeStatusBarOrientationNotification通知也失效了。
3、UIInterfaceOrientationMask
UIInterfaceOrientationMask是為了集成多種UIInterfaceOrientation而定義的類型,和ViewController相關(guān),一共有7種
1)iOS中的UIInterfaceOrientationMask定義
- 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可以支持哪些界面方向。
- //支持界面直立
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
- return UIInterfaceOrientationMaskPortrait;
- }
總結(jié):UIDeviceOrientation(設(shè)備方向)和UIInterfaceOrientation(屏幕方向)是兩個(gè)不同的概念。前者代表了設(shè)備的一種狀態(tài),而后者是屏幕為了應(yīng)對不同的設(shè)備狀態(tài),做出的用戶界面上的響應(yīng)。在iOS設(shè)備旋轉(zhuǎn)時(shí),由UIKit接收到旋轉(zhuǎn)事件,然后通過AppDelegate通知當(dāng)前程序的UIWindow對象,UIWindow對象通知它的rootViewController,如果該rootViewController支持旋轉(zhuǎn)后的屏幕方向,完成旋轉(zhuǎn),否則不旋轉(zhuǎn);彈出的ViewController也是如此處理。
二、視圖控制器中旋轉(zhuǎn)方向的設(shè)置
0、關(guān)于禁止橫屏的操作(不建議)
比較常規(guī)的方法有兩種。
方法1:在項(xiàng)目的General–>Deployment Info–>Device Orientation中,只勾選Portrait(豎屏)
勾選Portrait.png
方法2:Device Orientation默認(rèn)設(shè)置,在Appdelegate中實(shí)現(xiàn)supportedInterfaceOrientationsForWindow:只返回UIInterfaceOrientationMaskPortraitt(豎屏)
- - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
- return UIInterfaceOrientationMaskPortrait;
- }
說明:極少的APP中所有界面都是豎屏的,因?yàn)榭倳薪缑嫘枰С謾M屏,如視頻播放頁。所以不建議設(shè)置禁止APP頁面橫屏。
下面介紹如何讓項(xiàng)目中的 視圖控制器中旋轉(zhuǎn)方向的設(shè)置
1、APP支持多個(gè)方向
APP支持多個(gè)方向.png
說明:如此,APP支持橫屏和豎屏了,但是具體視圖控制器支持的頁面方向還需要進(jìn)一步處理。由于不支持豎屏顛倒(Upside Down),即使設(shè)備上下顛倒,通過API也不會獲得設(shè)備、屏幕上下顛倒方向的。
2、支持ViewController屏幕方向設(shè)置
1)關(guān)鍵函數(shù)
視圖控制器支持的界面方向主要由以下三個(gè)函數(shù)控制
- //是否自動旋轉(zhuǎn),返回YES可以自動旋轉(zhuǎn),返回NO禁止旋轉(zhuǎn)
- - (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
- //返回支持的方向
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
- //由模態(tài)推出的視圖控制器 優(yōu)先支持的屏幕方向
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
2) QSBaseViewController設(shè)置
- //QSBaseViewController.h
- @interface QSBaseController : UIViewController
- @end
- //QSBaseViewController.m
- @implementation QSBaseController
- //#pragma mark - 控制屏幕旋轉(zhuǎn)方法
- //是否自動旋轉(zhuǎn),返回YES可以自動旋轉(zhuǎn),返回NO禁止旋轉(zhuǎn)
- - (BOOL)shouldAutorotate{
- return NO;
- }
- //返回支持的方向
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
- return UIInterfaceOrientationMaskPortrait;
- }
- //由模態(tài)推出的視圖控制器 優(yōu)先支持的屏幕方向
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
- return UIInterfaceOrientationPortrait;
- }
- @end
說明1:QSBaseViewController默認(rèn)不支持旋轉(zhuǎn),只支持 界面豎直方向,項(xiàng)目中的Controller都繼承自QSBaseViewController,可以通過重寫這三個(gè)方法來讓Controller支持除豎屏之外的方向或旋轉(zhuǎn)。
3) 在QSNavigationController設(shè)置
目標(biāo):通過QSNavigationController來push視圖控制器時(shí),把支持屏幕旋轉(zhuǎn)的設(shè)置交給最新push進(jìn)來([self.viewControllers lastObject])的viewController來設(shè)置。
- //QSNavigationController.h
- @interface QSNavigationController : UINavigationController
- @end
- //QSNavigationController.m
- @implementation QSNavigationController
- #pragma mark - 控制屏幕旋轉(zhuǎn)方法
- - (BOOL)shouldAutorotate{
- return [[self.viewControllers lastObject]shouldAutorotate];
- }
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
- return [[self.viewControllers lastObject]supportedInterfaceOrientations];
- }
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
- return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
- }
- @end
4) 在QSTabBarController設(shè)置
目標(biāo):TabBarController通常作為整個(gè)程序的rootViewController,UITabBar上面顯示的每一個(gè)Tab都對應(yīng)著一個(gè)ViewController;每點(diǎn)擊一個(gè)Tab,出現(xiàn)的ViewController(self.selectedViewController)對屏幕旋轉(zhuǎn)和支持方向的設(shè)置 交給其自身去控制。
- //QSTabBarController.h
- @interface QSTabBarController : UITabBarController
- @end
- //QSTabBarController.m
- @implementation QSTabBarController
- #pragma mark - 控制屏幕旋轉(zhuǎn)方法
- - (BOOL)shouldAutorotate{
- return [self.selectedViewController shouldAutorotate];
- }
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
- return [self.selectedViewController supportedInterfaceOrientations];
- }
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
- return [self.selectedViewController preferredInterfaceOrientationForPresentation];
- }
- @end
三、屏幕旋轉(zhuǎn)方向下的視圖處理
1、屏幕旋轉(zhuǎn)時(shí),建議監(jiān)聽UIApplicationDidChangeStatusBarOrientationNotification
原因1:supportedInterfaceOrientations方法中最終返回的是 多個(gè)界面方向。
原因2(最重要的原因):我們真正要處理的是頁面方向發(fā)生旋轉(zhuǎn)UI的變化。而在設(shè)備的物理方向發(fā)生旋轉(zhuǎn)的時(shí)候,如果此時(shí)當(dāng)前控制器的頁面并沒有旋轉(zhuǎn),我們這時(shí)改變UI布局,可能就發(fā)生問題了。
2、屏幕的寬高處理
1)在iOS 8之后,當(dāng)屏幕旋轉(zhuǎn)的時(shí)候,[[UIScreen mainScreen] bounds]也發(fā)生了改變。如橫屏?xí)r候的屏幕寬度 其實(shí)是豎屏的時(shí)候屏幕的高度。
2)我們處理視圖布局時(shí)候,如果使用到屏幕的寬高,不要直接使用SCREEN_HEIGHT和SCREEN_WIDTH,而使用SCREEN_MIN和SCREEN_MAX
- #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)
說明:豎屏?xí)r候,寬是SCREEN_MIN,高是SCREEN_MAX;橫屏?xí)r候,寬是SCREEN_MAX,高是SCREEN_MIN。
3、屏幕旋轉(zhuǎn)下處理Demo
- //監(jiān)聽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];
- }
說明:當(dāng)然也可以選擇使用Masonry這樣優(yōu)秀的AutoLayout布局第三方庫來處理,storyBoard來布局次之。
4、屏幕旋轉(zhuǎn)下處理Demo效果圖
豎屏下效果.png
橫屏下效果.png
5、屏幕旋轉(zhuǎn)處理的建議
1)旋轉(zhuǎn)前后,view當(dāng)前顯示的位置盡量不變
2)旋轉(zhuǎn)過程中,暫時(shí)界面操作的響應(yīng)
3)視圖中有tableview的話,旋轉(zhuǎn)后,強(qiáng)制 [tableview reloadData],保證在方向變化以后,新的row能夠充滿全屏。
四、強(qiáng)制橫屏
APP中某些頁面,如視頻播放頁,一出現(xiàn)就要求橫屏。這些橫屏頁面或模態(tài)彈出、或push進(jìn)來。
1、模態(tài)彈出ViewController情況下 強(qiáng)制橫屏的設(shè)置
- //QSShow3Controller.m
- - (BOOL)shouldAutorotate{
- return NO;
- }
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
- return UIInterfaceOrientationMaskLandscapeRight;
- }
- - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
- return UIInterfaceOrientationLandscapeRight;
- }
- //模態(tài)彈出
- QSShow3Controller *vc = [[QSShow3Controller alloc]init];
- [self presentViewController:vc animated:YES completion:nil];
說明:這種情況比較簡單處理。
2、push推入ViewController情況下 強(qiáng)制橫屏的設(shè)置
- //QSShow4Controller.m
- -(void)viewWillAppear:(BOOL)animated{
- [super viewWillAppear:animated];
- [self setInterfaceOrientation:UIInterfaceOrientationLandscapeRight];
- }
- //強(qiáng)制轉(zhuǎn)屏(這個(gè)方法最好放在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開始是因?yàn)榍皟蓚€(gè)參數(shù)已經(jīng)被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];
說明:蘋果不允許直接調(diào)用setOrientation方法,否則有被拒的風(fēng)險(xiǎn);使用NSInvocation對象給[UIDevice currentDevice]發(fā)消息,強(qiáng)制改變設(shè)備方向,使其頁面方向?qū)?yīng)改變,這是蘋果允許的。
五、其他
1、 APP啟動時(shí),手機(jī)橫屏下,首頁UI(該頁面只支持豎屏)出錯(cuò)(add by 2017/6/20)
- //設(shè)置設(shè)置狀態(tài)欄豎屏
- [[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait];
以上詳細(xì)源碼參考:QSRotationScreenDemo
https://github.com/buaa0300/QSKitDemo/tree/master/QSRotationScreenDemo