天天看點

iOS 螢幕旋轉的實踐解析一、快速實作旋轉二、旋轉後的 UI 布局更新三、相關問題四、相關枚舉值五、結語

摘要:如何更靈活便捷的實作自定義螢幕旋轉場景,本文帶你揭秘!

iOS 螢幕旋轉的實踐解析一、快速實作旋轉二、旋轉後的 UI 布局更新三、相關問題四、相關枚舉值五、結語

文|即構 iOS 應用開發團隊

螢幕旋轉是在視訊直播類 APP 中常見的場景,在即構科技之前釋出的 Roomkit SDK 中也有螢幕跟随手機自動旋轉的場景。

在 Roomkit SDK 自身開發和客戶接入的過程中我們也會發現,實作螢幕旋轉的需求往往沒有那麼順利,經常會出現無法旋轉、旋轉後布局适配等問題。

本篇文章根據我們以往的開發經驗整理了螢幕旋轉實作的相關實踐方法,解析在實作過程中遇到的常見問題。

一、快速實作旋轉

iOS 螢幕旋轉的實作涉及到一堆枚舉值和回調方法,對于沒有做過旋轉相關需求的開發來說,可能一上來就暈了,是以我們先動手,讓螢幕轉起來吧。

實作旋轉的方式主要有兩種,跟随手機感應旋轉和手動旋轉,接下來對這兩種方式進行逐一介紹。

方式一:跟随手機感應器旋轉

要實作自動跟随手機旋轉,首先要讓目前的視圖控制器實作以下三個方法:

/// 是否自動旋轉
- (BOOL)shouldAutorotate {
    return YES;
}

/// 目前 VC支援的螢幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}

/// 優先的螢幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}
           

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

  • shouldAutorotate 傳回 YES 表示跟随系統旋轉,但是受 supportedInterfaceOrientations 方法的傳回值影響,隻支援跟随手機傳感器旋轉到支援的方向。
  • preferredInterfaceOrientationForPresentation 需要傳回 supportedInterfaceOrientations中支援的方向,不然會發生 'UIApplicationInvalidInterfaceOrientation'崩潰。

方式二:手動旋轉

這種方式在很多視訊軟體中都很常見,點選按鈕後旋轉至橫屏。

這時需要在 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];
    }
}
           

場景應用

  • 自動旋轉

如果你的 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 并調用手動旋轉的方法,這樣處理後隻能手動旋轉的效果就實作了。

二、旋轉後的 UI 布局更新

通常情況下,應用旋轉到橫豎屏後,因為不同的寬高比會有不同 UI,是以在螢幕旋轉的場景中我們又需要解決旋轉後 UI 适配的問題。

手機旋轉時,正常情況下若 shouldAutorotate 傳回 YES , 當視圖控制器需要旋轉就會觸發 viewWillTransitionToSize 方法,這樣我們就找到了去更新橫豎屏 UI 的時機了,也就是在 completion block 裡去完成旋轉後的适配邏輯。

/*
This method is called when the view controller's view's size is
changed by its parent (i.e. for the root view controller when its window rotates or is resized).

If you override this method, you should either call super to
propagate the change to children or manually forward the 
change to children.
 */
- (void)viewWillTransitionToSize:(CGSize)size
       withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    
    [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        //橫屏:size.width > size.height
        //豎屏: size.width < size.height
        NSLog(@"旋轉完成,更新布局");
    
    }];
}
           

三、相關問題

在開發旋轉場景的需求的時候,由于複雜的多級配置和數目繁多的枚舉類型,難免會遇到一些崩潰和無法旋轉的問題,下面我們就來總結一下此類問題。

問題一:無法自動旋轉

首先檢查下系統螢幕旋轉開關是否被鎖定。系統螢幕鎖定開關打開後,應用内無法自動旋轉,但是可以調用上文提到的的方法進行手動旋轉。

iOS 螢幕旋轉的實踐解析一、快速實作旋轉二、旋轉後的 UI 布局更新三、相關問題四、相關枚舉值五、結語

問題二:多級螢幕旋轉控制設定錯誤

以下方法都可以設定螢幕旋轉的全局權限:

  • Device Orientation 屬性配置:“TARGETS > General > Deployment Info > Device Orientation”,圖中是 xcode 預設的配置,值得注意的是 iPhone 不支援旋轉到 Upside Down 方向。
iOS 螢幕旋轉的實踐解析一、快速實作旋轉二、旋轉後的 UI 布局更新三、相關問題四、相關枚舉值五、結語
  • Appdelegate的 supportedInterfaceOrientationsForWindow 方法:
// 傳回需要支援的方向
// 如果我們實作了Appdelegate的這一方法,那麼我們的App的全局旋轉設定将以這裡的為準
- (UIInterfaceOrientationMask)application:(UIApplication *)applicatio supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window {
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskPortrait;
}
           

以上兩種方式優先級:Appdelegate方法 > Target配置,這兩種方式的配置和控制器的 supportedInterfaceOrientations 方法都會影響最終視圖控制器最終支援的方向。

以 iOS 14 中以 present 打開控制器的方式為例,目前控制器最終支援的螢幕方向,取決于上面兩種方式中的優先級最高的方式的值,與控制器 supportedInterfaceOrientations 的交集。

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

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

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

問題三:橫屏時打開系統鎖定螢幕開關,視圖被強制恢複到豎屏

由于 iOS 閉源,蘋果為什麼會這樣操作當然我們也無從得知,但是我們可以通過一些手段來規避這個問題。好在産生這樣的旋轉時,系統也會觸發和普通旋轉時一樣的方法調用。

以 iPhone X 為例,當下拉打開控制頁面時,我們會收到 UIApplicationWillResignActiveNotification 的系統通知,收起控制頁面後會收到 UIApplicationDidBecomeActiveNotification 通知,通過這兩個通知來記錄一下狀态,在 shouldAutorotate 通過判斷是否是 Active 狀态 傳回 YES/NO。

- (void)setupNotification {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification object:nil];
}

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

問題四:螢幕旋轉與 ZegoExpressEngine 的适配

有很多小夥伴已經接入了我們的 ZegoExpressEngine 實時音視訊引擎,那麼在旋轉的場景中你就要考慮到旋轉對推拉流的影響,以 RoomKit SDK 的使用場景為例,大緻有以下幾種情況:

  • 目前頁面固定一個方向顯示,隻需要設定與目前方向符合的視訊分辨率(引擎預設值為 “360 × 640”,根據自己需求确定),再調用引擎的 setAppOrientation 接口設定目前方向,以下代碼以左橫屏方向為例:
ZegoVideoConfig *videoConfig = [[ZegoVideoConfig alloc] init];
// 左橫屏分辨率設定如下:
videoConfig.encodeResolution = CGSizeMake(1280, 720);
[[ZegoExpressEngine sharedEngine] setVideoConfig:videoConfig];
// 調用 setAppOrientation 接口設定視訊的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:UIInterfaceOrientationLandscapeLeft];
           
  • 目前頁面有旋轉的場景,這時就需要在旋轉完成後去更新 ZegoExpressEngine 引擎的方向和視訊分辨率,注意這裡的目前方向取的是目前狀态欄的方向。
// 根據目前方向設定分辨率
ZegoVideoConfig *videoConfig = [ZegoVideoConfig defaultConfig];
if (isCurPortrait) {
    videoConfig.captureResolution = CGSizeMake(720, 1280);
} else {
    videoConfig.captureResolution = CGSizeMake(1280, 720);
}
// 調用 setAppOrientation 接口設定視訊的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:[UIApplication sharedApplication].statusBarOrientation];
           
  • 上面的 ZegoExpressEngine 音視訊引擎螢幕旋轉後的适配邏輯,處理時機都在視圖控制器旋轉完成後,也就是 viewWillTransitionToSize 方法的 completion block 裡面,這時拿到的 [UIApplication sharedApplication].statusBarOrientation 方向與目前控制器方向符合。

    (更多 ZegoExpressEngine 音視訊引擎螢幕旋轉問題可以參考:iOS 實時音視訊SDK視訊旋轉功能- 開發者中心 - ZEGO即構科技)

四、相關枚舉值

在前面的講述中,我們也認識了一些與螢幕旋轉相關的枚舉值。乍一看這塊内容确實會感覺多得讓人眼花缭亂,但是我們看清楚他們名稱中的關鍵詞如:Device、Interface,并在各個枚舉類型用到的地方去了解它的意思,也是能理清這裡面的邏輯的。

1、 裝置方向:UIDeviceOrientation

UIDeviceOrientation 是以 home 鍵的位置作為參照,受傳感器影響,和目前螢幕顯示的方向無關,是以隻能取值不能設值。

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

​​​​​​​前面講述的螢幕旋轉方法中不會直接用到這個枚舉,但是如果你有監聽裝置目前方向的需求時,它就變得有用了。可以通過 [UIDevice currentDevice].orientation 擷取目前裝置的方向,若要監聽裝置的方向變化,可以用以下代碼實作:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
 [[NSNotificationCenter defaultCenter] addObserver:observer
                                          selector:@selector(onDeviceOrientationChange:)
                                              name:UIDeviceOrientationDidChangeNotification
                                            object:nil];
           

2、 頁面方向:UIInterfaceOrientation

UIInterfaceOrientation 是目前視圖控制器的方向,差別于裝置方向,它是螢幕正在顯示的方向,preferredInterfaceOrientationForPresentation 方法的傳回值就是這個枚舉類型。

/// 優先的螢幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}
           

注意 UIInterfaceOrientationLandscapeLeft 與 UIDeviceOrientationLandscapeRight 是對應的,這兩個枚舉類型左右相反。

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

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);
           

五、結語

ZEGO RoomKit SDK 目前已經支援螢幕旋轉場景,并且在 2.0.0 版本中以 JSON 配置的形式,支援更靈活更便捷的實作自定義的螢幕旋轉場景。

在視訊直播類的 APP 中螢幕旋轉往往是繞不開的一環,梳理清楚以上三個枚舉的含義,以及旋轉方法的調用時機,并在恰當的時間去重新整理旋轉後的布局,iOS旋轉适配就不再困難。

以上就是關于在 iOS 上實作螢幕旋轉的技術解讀,也歡迎大家使用 RoomKit SDK 體驗 demo,點選連結,即可進行體驗:開發者中心 - 即構科技

iOS 螢幕旋轉的實踐解析一、快速實作旋轉二、旋轉後的 UI 布局更新三、相關問題四、相關枚舉值五、結語

​​​​​​​