iOS實錄11:代碼處理iOS的橫豎屏旋轉

[這是第11篇]

導語: iOS App中大多數(shù)頁面是只展示豎屏下的效果,但是少部分頁面需要支持橫豎屏。本文分別介紹監(jiān)聽屏幕旋轉方向視圖控制器中旋轉方向的設置屏幕旋轉方向下的視圖處理以及 強制橫屏的處理

一、監(jiān)聽屏幕旋轉方向

在處理iOS橫豎屏時,經(jīng)常會和UIDeviceOrientation、UIInterfaceOrientation和UIInterfaceOrientationMask這三個枚舉類型打交道,它們從不同角度描述了屏幕旋轉方向。

1、UIDeviceOrientation:設備方向

iOS的設備方向是通過iOS的加速計來獲取的。

1)iOS定義了以下七種設備方向

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,                 // 未知方向,可能是設備(屏幕)斜置
    UIDeviceOrientationPortrait,                // 設備(屏幕)直立
    UIDeviceOrientationPortraitUpsideDown,      // 設備(屏幕)直立,上下顛倒
    UIDeviceOrientationLandscapeLeft,           // 設備(屏幕)向左橫置
    UIDeviceOrientationLandscapeRight,          // 設備(屏幕)向右橫置
    UIDeviceOrientationFaceUp,                  // 設備(屏幕)朝上平躺
    UIDeviceOrientationFaceDown                 // 設備(屏幕)朝下平躺
};

說明:UIDeviceOrientation參考home鍵方向,如:home方向在右,設備(屏幕)方向向左(UIDeviceOrientationLandscapeLeft)

2)讀取設備方向

UIDevice單例代表當前的設備。從這個單例中可以獲得的信息設備,如設備方向orientation。

UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;

3)監(jiān)聽、處理和移除 設備方向改變的通知

當設備方向變化時候,發(fā)出UIDeviceOrientationDidChangeNotification通知;注冊監(jiān)聽該通知,可以針對不同的設備方向處理視圖展示。

//開啟和監(jiān)聽 設備旋轉的通知(不開啟的話,設備方向一直是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定義了以下五種界面方向

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,       //未知方向
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,               //界面直立
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,  //界面直立,上下顛倒
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,   //界面朝左
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft    //界面朝右
} __TVOS_PROHIBITED;

說明:從定義可知,界面方向和設別方向有對應關系,如界面的豎直方向就是 設備的豎直方向:UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown

2)讀取界面方向

UIInterfaceOrientation和狀態(tài)欄有關,通過UIApplication的單例調(diào)用statusBarOrientation來獲取

 UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];

3)監(jiān)聽、處理和移除 界面方向改變的通知

當界面方向變化時候,先后發(fā)出UIApplicationWillChangeStatusBarOrientationNotification和 ** UIApplicationDidChangeStatusBarOrientationNotification**通知;注冊監(jiān)聽這兩個通知,可以針對不同的界面方向處理視圖展示。

//以監(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];
}

說明:手機鎖定豎屏后,UIApplicationWillChangeStatusBarOrientationNotification和 ** UIApplicationDidChangeStatusBarOrientationNotification**通知也失效了。

3、UIInterfaceOrientationMask

UIInterfaceOrientationMask是為了集成多種UIInterfaceOrientation而定義的類型,和ViewController相關,一共有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;
}

總結:UIDeviceOrientation(設備方向)UIInterfaceOrientation(屏幕方向)是兩個不同的概念。前者代表了設備的一種狀態(tài),而后者是屏幕為了應對不同的設備狀態(tài),做出的用戶界面上的響應。在iOS設備旋轉時,由UIKit接收到旋轉事件,然后通過AppDelegate通知當前程序的UIWindow對象,UIWindow對象通知它的rootViewController,如果該rootViewController支持旋轉后的屏幕方向,完成旋轉,否則不旋轉;彈出的ViewController也是如此處理。

二、視圖控制器中旋轉方向的設置####

0、關于禁止橫屏的操作(不建議)#####

比較常規(guī)的方法有兩種。

方法1:在項目的General-->Deployment Info-->Device Orientation中,只勾選Portrait(豎屏)

勾選Portrait.png

方法2:Device Orientation默認設置,在Appdelegate中實現(xiàn)supportedInterfaceOrientationsForWindow:只返回UIInterfaceOrientationMaskPortraitt(豎屏)

-  (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window  {  
     return UIInterfaceOrientationMaskPortrait;  
}

說明:極少的APP中所有界面都是豎屏的,因為總會有界面需要支持橫屏,如視頻播放頁。所以不建議設置禁止APP頁面橫屏

下面介紹如何讓項目中的 視圖控制器中旋轉方向的設置

1、APP支持多個方向
APP支持多個方向.png

說明:如此,APP支持橫屏和豎屏了,但是具體視圖控制器支持的頁面方向還需要進一步處理。由于不支持豎屏顛倒(Upside Down),即使設備上下顛倒,通過API也不會獲得設備、屏幕上下顛倒方向的。

2、支持ViewController屏幕方向設置

1)關鍵函數(shù)

視圖控制器支持的界面方向主要由以下三個函數(shù)控制

//是否自動旋轉,返回YES可以自動旋轉,返回NO禁止旋轉  
- (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設置 **

//QSBaseViewController.h
@interface QSBaseController : UIViewController

@end

//QSBaseViewController.m
@implementation QSBaseController

  //#pragma mark - 控制屏幕旋轉方法
//是否自動旋轉,返回YES可以自動旋轉,返回NO禁止旋轉
- (BOOL)shouldAutorotate{
    return NO;
}

//返回支持的方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait;
}

//由模態(tài)推出的視圖控制器 優(yōu)先支持的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationPortrait;
}
@end

說明1:QSBaseViewController默認不支持旋轉,只支持 界面豎直方向,項目中的Controller都繼承自QSBaseViewController,可以通過重寫這三個方法來讓Controller支持除豎屏之外的方向或旋轉。

**3) 在QSNavigationController設置 **

目標:通過QSNavigationController來push視圖控制器時,把支持屏幕旋轉的設置交給最新push進來([self.viewControllers lastObject])的viewController來設置。

//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,出現(xiàn)的ViewController(self.selectedViewController)對屏幕旋轉和支持方向的設置 交給其自身去控制。

//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、屏幕旋轉時,建議監(jiān)聽UIApplicationDidChangeStatusBarOrientationNotification#####

原因1:supportedInterfaceOrientations方法中最終返回的是 多個界面方向

原因2(最重要的原因):我們真正要處理的是頁面方向發(fā)生旋轉UI的變化。而在設備的物理方向發(fā)生旋轉的時候,如果此時當前控制器的頁面并沒有旋轉,我們這時改變UI布局,可能就發(fā)生問題了。

2、屏幕的寬高處理

1)在iOS 8之后,當屏幕旋轉的時候,[[UIScreen mainScreen] bounds]也發(fā)生了改變。如橫屏時候的屏幕寬度 其實是豎屏的時候屏幕的高度。

2)我們處理視圖布局時候,如果使用到屏幕的寬高,不要直接使用SCREEN_HEIGHTSCREEN_WIDTH,而使用SCREEN_MINSCREEN_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)

說明:豎屏時候,寬是SCREEN_MIN,高是SCREEN_MAX;橫屏時候,寬是SCREEN_MAX,高是SCREEN_MIN

3、屏幕旋轉下處理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];
}

說明:當然也可以選擇使用Masonry這樣優(yōu)秀的AutoLayout布局第三方庫來處理,storyBoard來布局次之。

4、屏幕旋轉下處理Demo效果圖#####
豎屏下效果.png
橫屏下效果.png
5、屏幕旋轉處理的建議#####

1)旋轉前后,view當前顯示的位置盡量不變

2)旋轉過程中,暫時界面操作的響應

3)視圖中有tableview的話,旋轉后,強制 [tableview reloadData],保證在方向變化以后,新的row能夠充滿全屏。

四、強制橫屏

APP中某些頁面,如視頻播放頁,一出現(xiàn)就要求橫屏。這些橫屏頁面或模態(tài)彈出、或push進來。

1、模態(tài)彈出ViewController情況下 強制橫屏的設置
//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情況下 強制橫屏的設置
//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開始是因為前兩個參數(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方法,否則有被拒的風險;使用NSInvocation對象給[UIDevice currentDevice]發(fā)消息,強制改變設備方向,使其頁面方向對應改變,這是蘋果允許的。

五、其他

  • 1、 APP啟動時,手機橫屏下,首頁UI(該頁面只支持豎屏)出錯(add by 2017/6/20)

      //設置設置狀態(tài)欄豎屏
      [[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait];
    
  • 以上詳細源碼參考QSRotationScreenDemo

  • 我是南華coder,一名北漂的初級iOS程序猿。iOS實(踐)錄系列是我的一點開發(fā)心得,希望能夠拋磚引玉。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 1.監(jiān)聽屏幕旋轉方向 在處理iOS橫豎屏時,經(jīng)常會和UIDeviceOrientation、UIInterface...
    彬至睢陽閱讀 2,555評論 1 6
  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,251評論 4 61
  • 到現(xiàn)在,我也說不清楚自己理解的到底和看到的是不是一個意思。但是,這好像真的幫我看清了現(xiàn)實,讓我從一定程度上消除了幻...
    BelovedNutan閱讀 380評論 0 0
  • 今天禮拜天,但是兒子又去輔導班,下午放學后回家問我“媽媽,他們今天都休息了,就我們幾個在那練習,我為什么不能休息呢...
    俊好媽咪閱讀 406評論 0 0