本篇主要通過四個方面來解析屏幕旋轉:
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.1
和1.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);
結語
路漫漫其修遠兮,吾將上下而求索~
.End