iOS 關于屏幕旋轉(zhuǎn)問題

關于屏幕旋轉(zhuǎn)需要理解兩個概念設備方向(UIDeviceOrientation)和屏幕方向(UIInterfaceOrientation)
其中設備方向是物理方向,屏幕方向是APP內(nèi)容顯示方向,我們基本都是跟屏幕方向打交道,設備方向,我們只需要取當前設備方向值就OK了。
其中屏幕旋轉(zhuǎn)是建立在手機加速計基礎。

基礎知識

1. UIDeviceOrientation(設備的物理方向)

UIDeviceOrientation是我們手持的蘋果設備(iPhone,iPad..)的當前的朝向,是實物,共有七個方向,是以home鍵為基礎參照物的。
home鍵在左時,屏幕是向右旋轉(zhuǎn)(UIDeviceOrientationLandscapeRight),home鍵在右時,屏幕是向左旋轉(zhuǎn)(UIDeviceOrientationLandscapeLeft)。
當前屏幕的方向通過[UIDevice currentDevice].orientation方法獲取,這個方法我們只能讀取值,不能設置值,因為這是物理方向。
如果頁面的不支持自動旋轉(zhuǎn)功能我們獲取的值只能是UIDeviceOrientationPortrait

//Portrait 表示縱向,Landscape 表示橫向。
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
      //未知方向,可能是設備(屏幕)斜置
     UIDeviceOrientationUnknown,
    //設備(屏幕)豎屏
     UIDeviceOrientationPortrait,           // Device oriented vertically, home button on the bottom
    //豎屏,只不過上下顛倒
     UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
    //設備向左旋轉(zhuǎn)橫置
     UIDeviceOrientationLandscapeLeft,      // Device oriented horizontally, home button on the right
    //設備向右旋轉(zhuǎn)橫置
     UIDeviceOrientationLandscapeRight,     // Device oriented horizontally, home button on the left
    //設備(屏幕)朝上平躺
     UIDeviceOrientationFaceUp,             // Device oriented flat, face up
    //設備(屏幕)朝下平躺
     UIDeviceOrientationFaceDown            // Device oriented flat, face down

   } __TVOS_PROHIBITED;

2. UIInterfaceOrientation(界面的顯示方向)

UIInterfaceOrientation界面的當前旋轉(zhuǎn)方向或者說是朝向(如果當前頁面支持屏幕旋轉(zhuǎn)就算設備旋轉(zhuǎn)鎖關閉了也可以強制屏幕旋轉(zhuǎn)),屏幕方向和設備方向的區(qū)別是一個是可以設置一個無能為力。

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    //屏幕方向未知
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    //向上正方向的豎屏
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    //向下正方向的豎屏
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    //向右旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    //向左旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;

其中兩個枚舉值中的左右旋轉(zhuǎn)剛好對立,當設備向左轉(zhuǎn)時屏幕是向右轉(zhuǎn)的。

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, 
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

3.屏幕旋轉(zhuǎn)流程

加速計是屏幕旋轉(zhuǎn)的基礎,依賴加速計,設備才可以判斷出當前的設備方向。
當加速計檢測到方向變化的時候,會發(fā)出UIDeviceOrientationDidChangeNotification通知。

APP處理屏幕旋轉(zhuǎn)的流程

  1. 當設備加速計檢測到方向變化的時候,會發(fā)出UIDeviceOrientationDidChangeNotification屏幕旋轉(zhuǎn)通知,這樣任何關心方向變化的View都可以通過注冊該通知,在設備方向變化的時候做出相應的響應。
  2. 設備旋轉(zhuǎn)的后,APP內(nèi)接收到旋轉(zhuǎn)事件(通知)。
  3. APP通過AppDelegate通知當前程序的KeyWindow。
  4. KeyWindow會知會它的rootViewController,判斷該View Controller所支持的旋轉(zhuǎn)方向,完成旋轉(zhuǎn)。
  5. 如果存在彈出的View Controller(模態(tài)彈的)的話,系統(tǒng)則會根據(jù)彈出的View Controller,來判斷是否要進行旋轉(zhuǎn)。

APP可以選擇性的(是否)接收 UIDeviceOrientationDidChangeNotification通知。

// 是否已經(jīng)開啟了設備方向改變的通知
@property(nonatomic,readonly,getter=isGeneratingDeviceOrientationNotifications)
BOOL generatesDeviceOrientationNotifications
__TVOS_PROHIBITED;

// 開啟接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)beginGeneratingDeviceOrientationNotifications
__TVOS_PROHIBITED;     // nestable

// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)endGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED;

在 app 代理里面結(jié)束接收 設備旋轉(zhuǎn)的通知事件, 后續(xù)的屏幕旋轉(zhuǎn)都會失效

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    return YES;
}

UIViewController實現(xiàn)屏幕旋轉(zhuǎn)

在響應設備旋轉(zhuǎn)時,我們可以通過UIViewController的方法實現(xiàn)更細致控制,當View Controller接收到Window傳來的方向變化的時候,流程如下:

  1. 首先判斷當前ViewController是否支持旋轉(zhuǎn)到目標方向,如果支持的話進入流程2,否則此次旋轉(zhuǎn)流程直接結(jié)束。
  2. 調(diào)用 willRotateToInterfaceOrientation:duration: 方法,通知View Controller將要旋轉(zhuǎn)到目標方向。如果該ViewController是一個Container View Controller的話,它會繼續(xù)調(diào)用其Content View Controller的該方法。這個時候我們也可以暫時將一些View隱藏掉,等旋轉(zhuǎn)結(jié)束以后在現(xiàn)實出來。
  3. Window調(diào)整顯示的View Controller的bounds,由于View Controller的bounds發(fā)生變化,將會觸發(fā) viewWillLayoutSubviews 方法。這個時候self.interfaceOrientation和statusBarOrientation方向還是原來的方向。
  4. 接著當前View Controller的 willAnimateRotationToInterfaceOrientation:duration: 方法將會被調(diào)用。系統(tǒng)將會把該方法中執(zhí)行的所有屬性變化放到動animation block中。
  5. 執(zhí)行方向旋轉(zhuǎn)的動畫。
  6. 最后調(diào)用 didRotateFromInterfaceOrientation: 方法,通知View Controller旋轉(zhuǎn)動畫執(zhí)行完畢。這個時候我們可以將第二部隱藏的View再顯示出來。

響應過程如下圖所示:


響應過程

4. 監(jiān)聽屏幕旋轉(zhuǎn)方向

UIDeviceOrientationDidChangeNotification

 //添加監(jiān)聽設備方向的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil];


//監(jiān)聽設備方向的通知方法
#pragma mark - 監(jiān)聽設備方向和全屏
//參照坐標 手機在豎屏情況下
- (void)onDeviceOrientationChange{
    UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
    UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
    switch (interfaceOrientation) {
        case UIInterfaceOrientationPortraitUpsideDown:{
            NSLog(@"狀態(tài)欄在手機下方 不過一般不會用到");
        }
            break;
        case UIInterfaceOrientationPortrait:{
            NSLog(@"手機在豎屏狀態(tài)下");
        }
            break;
        case UIInterfaceOrientationLandscapeLeft:{
            NSLog(@"狀態(tài)欄在手機左側(cè)");
        }
            break;
        case UIInterfaceOrientationLandscapeRight:{
            NSLog(@"狀態(tài)欄在手機右側(cè)");
        }
            break;
        default:
            break;
    }
}

界面發(fā)生變化狀態(tài)欄改變通知
UIApplicationWillChangeStatusBarOrientationNotification
UIApplicationDidChangeStatusBarOrientationNotification

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

- (void)dealloc{
//最后在dealloc中移除通知
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    [[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
}

PS:手機鎖定豎屏后,UIApplicationWillChangeStatusBarOrientationNotification,UIApplicationDidChangeStatusBarOrientationNotificationUIDeviceOrientationDidChangeNotification通知都會失效。

具體應用

前提:想要APP支持橫豎屏或者某個頁面或者功能可以橫豎屏切換,前提是項目支持橫豎屏不然強制橫屏也沒用,需要我們的TARGETS中或者info.plist文件中右或者appdelegate中設置。我們在項目中屏幕旋轉(zhuǎn)的方向就是這些設置的屏幕支持的方向,如果超出項目設置好的屏幕朝向APP會crash。

project > TARGETS > Gengral > Deployment Info > Device Orientation

TARGETS Device Orientation

info.plist

appdelegate

項目中是否可以旋轉(zhuǎn)主要由下面這三個方法控制
- (BOOL) shouldAutorotate;
- (UIInterfaceOrientationMask) supportedInterfaceOrientations;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;

一. shouldAutorotate(是否支持自動旋轉(zhuǎn))

- (BOOL) shouldAutorotate{
    return YES;
}

shouldAutorotate作用是調(diào)用這個方法的控制器是否支持自動旋轉(zhuǎn),使用這個方法前提是需要項目支持橫屏,返回值是BOOL。

情景:

  1. NO 當前頁面不可以自動橫屏,手機豎屏鎖在打開或者關閉,調(diào)用強制橫屏方法無用,不可以橫屏。
//手機強制橫屏方法
NSNumber *orientation = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setValue:orientation forKey:@"orientation"];
  1. YES 當前頁面支持自動旋轉(zhuǎn),手機豎屏鎖打開,不可以根據(jù)手機方向旋轉(zhuǎn),調(diào)用強制橫屏方法頁面可以橫屏。
  2. YES 手機豎屏鎖關閉,屏幕可以根據(jù)手機朝向旋轉(zhuǎn),也可以強制旋轉(zhuǎn)。

二. supportedInterfaceOrientations(當前屏幕支持的方向)

- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAllButUpsideDown;
}

supportedInterfaceOrientations作用是屏幕支持的方向有哪些,這個方法返回值是個枚舉值UIInterfaceOrientationMask具體值有下面

typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    //向上為正方向的豎屏
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    //向左移旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    //向右旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    //向下為正方向的豎屏
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    //向左或者向右的橫屏
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    //所有的橫豎屏方向都支持
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    //支持向上的豎屏和左右方向的橫屏
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} __TVOS_PROHIBITED;

三. preferredInterfaceOrientationForPresentation

preferredInterfaceOrientationForPresentation 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)返回值是UIInterfaceOrientation是個枚舉值

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    //屏幕方向未知
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    //向上正方向的豎屏
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    //向下正方向的豎屏
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    //向右旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    //向左旋轉(zhuǎn)的橫屏
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;

四. 具體實現(xiàn)

以上三個方法如果是在隨便寫的一個demo中完全OK,但是要是在完整項目中,會無效。

原因:
測試結(jié)果是當前控制器(頁面)是否支持旋轉(zhuǎn)是由根視圖控制器控制的也就是rootViewController,跟視圖控制器如果沒有重寫上面三個方法默認是支持自動旋轉(zhuǎn)的。因為隨便寫的demo里面的ViewControl就是根視圖控制器所以有效。

一般情況下我們的根視圖控制器要么是navigationController要么是tabbarController也有ViewControl。所以我們在根視圖控制器下重寫上面三個方法,把是否支持橫豎屏給需要橫豎屏的頁面控制器來控制,或者寫一個category。

下面是navigationController重寫的方法其他的一樣

1. 導航根視圖控制器

導航根視圖控制器下重寫方法:

#import "HPNavigationController.h"

@implementation HPNavigationController


- (BOOL) shouldAutorotate{
    NSLog(@"%@",[UIApplication sharedApplication].keyWindow.rootViewController);

    return [self.visibleViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
    return [self.visibleViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return [self.visibleViewController preferredInterfaceOrientationForPresentation];
}


@end

category重寫方法:

#import "UINavigationController+HPToolBar.h"

@implementation UINavigationController (HPToolBar)

- (BOOL) shouldAutorotate{
    return [self.topViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
    return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation) preferredInterfaceOrientationForPresentation{
    return [self.topViewController preferredInterfaceOrientationForPresentation];
}
- (UIViewController *)childViewControllerForStatusBarStyle{
    return self.topViewController;
}
- (UIViewController *)childViewControllerForStatusBarHidden{
    return self.topViewController;
}


@end
1. tabBar根視圖控制器

tabBar根視圖控制器下重寫方法:

#import "HPUITabBarController.h"

@implementation HPUITabBarController


- (BOOL) shouldAutorotate{
    return [self.selectedViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
    return [self.selectedViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}


@end

category重寫方法:

#import "UITabBarController+HPToolBar.h"

@implementation UITabBarController (HPToolBar)


// 是否支持自動轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController shouldAutorotate];
    } else {
        return [vc shouldAutorotate];
    }
}

// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController supportedInterfaceOrientations];
    } else {
        return [vc supportedInterfaceOrientations];
    }
}

// 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController preferredInterfaceOrientationForPresentation];
    } else {
        return [vc preferredInterfaceOrientationForPresentation];
    }
}


@end

ViewController根視圖控制器 category重寫方法

#import "UIViewController+HPToolBar.h"

@implementation UIViewController (HPToolBar)


// 是否支持自動轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
    return NO;
}

// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

// 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

@end

寫過這三個方法后就可以在需要旋轉(zhuǎn)的控制器下重寫這三個方法,可以實現(xiàn)自動旋轉(zhuǎn)或者強制旋轉(zhuǎn),不過強制旋轉(zhuǎn)的前提是shouldAutorotate這個方法的返回值為YES不然強制旋轉(zhuǎn)無效。上面的執(zhí)行順序是先找根視圖下的方法,如果有會直接回調(diào)。如果沒有會找有沒有navigationController的類別,有就回調(diào),沒有就直接默認為YES。如果根視圖控制器沒有這重寫這三個方法,會找當前ViewControll繼承的視圖父類或者類別。在把橫屏配置打開情況下如果沒有重寫這三個方法頁面只支持豎屏(實測)。如果想要某個頁面支持旋轉(zhuǎn)只需要在支持旋轉(zhuǎn)的控制下重寫這三個方法。

有關shouldAutorotate調(diào)用沒反應的問題上面有解釋,是因為視圖是否旋轉(zhuǎn)是由根視圖控制器控制,只要重寫了上面的方法,就沒問題。

屏幕旋轉(zhuǎn)demo地址

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容