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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容