iOS 屏幕旋轉的實踐解析

本篇主要通過四個方面來解析屏幕旋轉:
1、實現旋轉的方式之跟隨手機感應旋轉
2、實現旋轉的方式之手動旋轉
3、屏幕旋轉的場景應用
4、易混淆的枚舉值
下面來逐條分析:

一、跟隨手機感應器旋轉

1. 兩種方法可以設置屏幕旋轉的全局權限:

1.1 Device Orientation 屬性配置:

新建項目 -> TARGET -> General -> Deployment Info


Device Orientation
  • iPhone 默認豎屏展示, 當系統屏幕旋轉開關未鎖定時, 就可以自由的轉動, 值得注意的是iPhone 不支持旋轉到 Upside Down 方向
  • iPad 默認豎屏展示, 當系統屏幕旋轉開關未鎖定時, 伴隨iPad方向展示, 就可以自由的轉動。
系統屏幕旋轉開關
1.2 實現Appdelegate 的supportedInterfaceOrientationsForWindow 方法:
// 返回需要支持的方向
// 如果我們實現了Appdelegate的這一方法,那么我們的App的全局旋轉設置將以這里的為準
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    return UIInterfaceOrientationMaskAll;
}

總結: 以上兩種方式優先級:Appdelegate方法 > Target 配置

2. 單一控制器處理屏幕跟隨手機感應器旋轉

只要讓當前的視圖控制器實現以下三個方法:

/** 是否自動旋轉
 - iOS16.0已過期, 不會再調用.
 - iOS16.0 之前, 先調用supportedInterfaceOrientations 確定支持的屏幕方向, 再調用shouldAutorotate 是否自動旋轉, 來確定控制器方向.
 - iOS16.0 之前, preferredInterfaceOrientationForPresentation 并未調用.
 - iOS16.0 之前, return NO 之后, 當前控制器都支持屏幕旋轉了
 */
- (BOOL)shouldAutorotate {
    return YES;
}

/// 當前控制器支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}

/// 優先的屏幕方向 - 只會在 presentViewController:animated:completion時被調用
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

這種方法需要注意以下幾點:

  • shouldAutorotate返回 YES 表示跟隨系統旋轉,但是受 supportedInterfaceOrientations 方法的返回值影響,只支持跟隨手機傳感器旋轉到支持的方向.
  • preferredInterfaceOrientationForPresentation 需要返回 supportedInterfaceOrientations 中支持的方向,不然會發生'UIApplicationInvalidInterfaceOrientation'崩潰.
  • iOS16.0 之后單純只會調用supportedInterfaceOrientations, shouldAutorotate就不會被調用了。

3. 既配置了屏幕旋轉的全局權限, 也配置了單一控制器屏幕旋轉權限

1.11.2兩種方式的配置和控制器的 supportedInterfaceOrientations 方法(2)都會影響最終視圖控制器最終支持的方向。

iOS 16 之前present打開控制器的方式為例,當前控制器最終支持的屏幕方向,取決于上面兩種方式中的優先級最高的方式的值,與控制器 supportedInterfaceOrientations 的交集。

總結起來有以下幾種情況:

  • 如果交集為空,且在控制器的 shouldAutorotate 方法中返回為YES,則會發生 UIApplicationInvalidInterfaceOrientation的崩潰。
  • 如果交集為空,且在控制器的 shouldAutorotate 方法中返回為NO,控制器的 supportedInterfaceOrientations 方法與 preferredInterfaceOrientationForPresentation方法返回值不沖突(前者返回值包含有后者返回值),則顯示為控制器配置的方向。
  • 如果交集為空,且在控制器的 shouldAutorotate 方法中返回為 NO,控制器的 supportedInterfaceOrientations方法與 preferredInterfaceOrientationForPresentation方法返回值沖突(前者返回值未包含有后者返回值),則會發生UIApplicationInvalidInterfaceOrientation 的崩潰。
  • 如果交集不為空,控制器的 supportedInterfaceOrientations 方法與preferredInterfaceOrientationForPresentation 方法返回值沖突,則會發生 UIApplicationInvalidInterfaceOrientation 的崩潰。
  • 如果交集不為空,控制器的 supportedInterfaceOrientations 方法與 preferredInterfaceOrientationForPresentation 方法返回值不沖突,當前控制器則根據 shouldAutorotate 返回值決定是否在交集的方向內自動旋轉。

所以, 這里建議如果沒有全局配置的需求,就不要變更 Target 屬性配置或實現 Appdelegate 方法,只需在要實現旋轉效果的 ViewController 中按前面所說的方式去實現代碼

而在 iOS 16之后, 所有崩潰的問題都被優化了, 同樣, 當前控制器最終支持的屏幕方向,取決于上面兩種方式中的優先級最高的方式的值,與控制器 supportedInterfaceOrientations 的交集。

總結起來有以下幾種情況:

  • 如果交集為空,以控制器supportedInterfaceOrientations方法返回值為準
  • 如果交集不為空,且控制器supportedInterfaceOrientations 方法與preferredInterfaceOrientationForPresentation方法返回值不沖突,則以交集為準

二、手動旋轉屏幕

這種方式在很多視頻軟件中都很常見,點擊按鈕后旋轉至橫屏。

在iOS 16 之前需要在 shouldAutorotate 中返回 yes,然后再在此方法中 UIInterfaceOrientation 傳入你需要旋轉到的方向。

- (void)changeVCToOrientation:(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]];
        int val = orientation;
        [invocation setArgument:&val atIndex:2];
        [invocation invoke];
    }
}

在iOS 16之后你會發現, 它已經失效了, fix如下:
升級了Xcode14+, 適配iOS16 API
未升級Xcode14+, 在舊版適配新版本 API

三、屏幕旋轉的場景應用

  • 自動旋轉
    如果你的 iPhone 沒有關閉系統屏幕旋轉,你就能發現系統相冊 APP 的頁面是可以跟著手機轉動方向旋轉的。

如果你想實現和它一樣的效果,只需要按照前面方式一(跟隨手機感應器旋轉)去配置你的視圖控制器的方法,之后控制器就可以在 supportedInterfaceOrientations返回的方向內實現自由旋轉了。

  • 只能手動旋轉
    這種場景比較少見,在視頻直播類 APP 中常見的場景是自動和手動旋轉相結合的方式。
    如果你要實現只能通過像點擊按鈕去旋轉的方式,首先需要在 supportedInterfaceOrientations 方法中返回你需要支持的方向,這里重點是 shouldAutorotate 方法的返回值。

上面方式二中(手動旋轉)說明了手動旋轉需要 shouldAutorotate 返回 YES,但是這也會讓控制器支持自動旋轉,不符合這個需求,所以我們按以下方法處理:

- (BOOL)shouldAutorotate {
    if (self.isRotationNeeded) {
        return YES;
    } else {
        return NO;
    }
}    

屬性 isRotationNeeded作為是否需要旋轉的標記,isRotationNeeded默認為 NO,此時就算你旋轉設備,回調 shouldAutorotate 方法時也不會返回 YES,所以屏幕也不會自動旋轉。
剩下的只需要你在點擊旋轉的按鈕后將isRotationNeeded 置為YES 并調用手動旋轉的方法,這樣處理后只能手動旋轉的效果就實現了。

4、相關枚舉值

4.1 設備方向:UIDeviceOrientation

UIDeviceOrientation 是以home 鍵的位置作為參照,枚舉值指的是設備倒向的方向, 受傳感器影響,和當前屏幕顯示的方向無關,所以只能取值不能設值
前面講述的屏幕旋轉方法中不會直接用到這個枚舉,但是如果你有監聽設備當前方向的需求時,它就變得有用了。可以通過 [UIDevice currentDevice].orientation 獲取當前設備的方向,若要監聽設備的方向變化,可以用以下代碼實現:

 [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
 [[NSNotificationCenter defaultCenter] addObserver:observer
                                          selector:@selector(onDeviceOrientationChange:)
                                              name:UIDeviceOrientationDidChangeNotification
                                            object:nil];
- (void)onDeviceOrientationChange:(NSNotification *)noti {
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
            NSLog(@"豎屏");
            break;
        case UIDeviceOrientationLandscapeLeft:
            NSLog(@"橫屏(Home在右邊,設備向左邊倒)");
            break;
        case UIDeviceOrientationLandscapeRight:
            NSLog(@"橫屏(Home在左邊,設備向右邊倒)");
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            NSLog(@"Home在上邊");
            break;
        case UIDeviceOrientationFaceUp:
            NSLog(@"屏幕向上");
            break;
        case UIDeviceOrientationFaceDown:
            NSLog(@"屏幕向下");
            break;
        default:
            break;
    }
}

4.2 頁面方向:UIInterfaceOrientation

UIInterfaceOrientation 是當前視圖控制器的方向,區別于設備方向,它是屏幕正在顯示的方向, 也就是當前頁面home所處的方向

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);

4.3 頁面方向:UIInterfaceOrientationMask

觀察 UIInterfaceOrientationMask 枚舉的值,我們就會發現這是一種為了支持多種 UIInterfaceOrientation 而定義的類型,它用來作為 supportedInterfaceOrientations 方法的返回值,比如我們在該方法中返回 UIInterfaceOrientationMaskAll 就可以支持所有方向了。

/// 當前 VC支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}
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),
} API_UNAVAILABLE(tvos);

結語

路漫漫其修遠兮,吾將上下而求索~

作者簡書

作者掘金

作者GitHub

.End

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

推薦閱讀更多精彩內容