一,前言
(1)何為橫屏/豎屏

橫屏

豎屏:
(2)是否啟動橫屏/豎屏切換的區別
App開啟橫/豎屏切換:
開啟橫豎屏時,當屏幕為橫屏時,系統window界面會以橫屏的左上角為坐標系原點;當屏幕為豎屏時window界面會以豎屏的左上角為坐標系原點。橫豎屏切換,坐標系切換。
App關閉橫/豎屏切換:
關閉橫豎屏時,當屏幕為橫屏時,系統window界面會以橫屏的左上角為坐標系原點;當屏幕為豎屏時window界面會以豎屏的左上角為坐標系原點。橫豎屏切換,坐標系不會切換,系統會以屏幕的初時坐標系為坐標原點。
(3)UIKit處理屏幕旋轉的流程
當加速計檢測到方向變化的時候,會發出 UIDeviceOrientationDidChangeNotification 通知,這樣任何關心方向變化的view都可以通過注冊該通知,在設備方向變化的時候做出相應的響應。
UIKit的響應應屏幕旋轉的流程如下:
1、設備旋轉的時候,UIKit接收到旋轉事件。 2、UIKit通過AppDelegate通知當前程序的window。 3、Window會知會它的rootViewController,判斷該view controller所支持的旋轉方向,完成旋轉。 4、如果存在彈出的view controller的話,系統則會根據彈出的view controller,來判斷是否要進行旋轉。
UIViewController實現屏幕旋轉如下:
在響應設備旋轉時,我們可以通過UIViewController的方法實現更細粒度的控制,當view controller接收到window傳來的方向變化的時候,流程如下:
1、首先判斷當前viewController是否支持旋轉到目標方向,如果支持的話進入流程2,否則此次旋轉流程直接結束。
2、調用 willRotateToInterfaceOrientation:duration: 方法,通知view controller將要旋轉到目標方向。如果該viewController是一個container view controller的話,它會繼續調用其content view controller的該方法。這個時候我們也可以暫時將一些view隱藏掉,等旋轉結束以后在現實出來。
3、window調整顯示的view controller的bounds,由於view controller的bounds發生變化,將會觸發 viewWillLayoutSubviews 方法。這個時候self.interfaceOrientation和statusBarOrientation方向還是原來的方向。
4、接着當前view controller的 willAnimateRotationToInterfaceOrientation:duration: 方法將會被調用。系統將會把該方法中執行的所有屬性變化放到動animation block中。
5、執行方向旋轉的動畫。
6、最后調用 didRotateFromInterfaceOrientation: 方法,通知view controller旋轉動畫執行完畢。這個時候我們可以將第二部隱藏的view再顯示出來。
整個響應過程如下圖所示:

二,如何設置橫豎屏切換
(1)橫豎屏方向枚舉
橫豎屏的三種枚舉,UIInterfaceOrientation,UIInterfaceOrientationMask,UIDeviceOrientation。
* UIInterfaceOrientation 當前頁面的方向 (以home在下為正豎屏方向,home鍵在右為左橫屏方向)
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, //正豎屏方向 UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, //反豎屏方向 UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, //左橫屏方向 UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft //右橫屏方向 }
* UIDeviceOrientation是設備的當前所處的方向,而且事實上它有6個值,
typedef NS_ENUM(NSInteger, UIDeviceOrientation) { UIDeviceOrientationUnknown, //未知方向 UIDeviceOrientationPortrait, //豎直 // Device oriented vertically, home button on the bottom UIDeviceOrientationPortraitUpsideDown, // 上下反轉 UIDeviceOrientationLandscapeLeft, // 向左旋轉 UIDeviceOrientationLandscapeRight, // 向右旋轉 UIDeviceOrientationFaceUp, // 屏幕朝上 UIDeviceOrientationFaceDown // 屏幕朝下 }
⚠️注意:
請仔細觀察上面的枚舉值。
在處於豎屏和上下翻轉的狀態下這兩個枚舉值是一樣的,而當處於橫屏時,這兩個值剛好相反。
所以在有時你發現跟你預期的翻轉方向不一樣的時候,可能你用錯了枚舉。
⚠️總結:在設備進行橫屏旋轉的時候,為了橫屏時上下不翻轉,所以當Device處於Left時,界面應該是Right旋轉。這樣才不會使橫屏時內容上下翻轉。所以我想你應該明白了為什么在處於橫屏時為什么他們倆的值是剛好相反的。
* UIInterfaceOrientationMask (橫豎屏適配時,最常用的是這個枚舉)
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), }
(2)開啟橫豎屏權限
第一種是在項目中直接進行勾選:
可以看到這種勾選方式允許你進行四個方向的配置,並且這種勾選方式會直接在你的項目plist文件中添加 (二者設置任何一方都會同步到另一方)

但是由於在這里配置是對項目啟動時lanuch界面產生影響,而往往我們又沒有對lanuch進行橫豎屏適配,所以在這個時候我們就需要使用第二種方式進行配置。
第二種:在項目中的AppDelegate文件中進行配置。
#pragma mark - InterfaceOrientation //應用支持的方向 - (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAllButUpsideDown; }
搭配UIInterfaceOrientationMask使用,你可以很方便的讓你項目開啟你所需要的橫豎屏權限和限制條件。
第三種:在VC中如何開啟橫豎屏
一般我們項目中不需要全部橫屏,僅支持豎屏,比如列表頁;有些頁面必須支持橫豎屏,比如視頻播放頁。這個時候我們就需要對相應的控制器做細致化操作。
(1)個別界面固定方向,其他所有界面都支持橫豎屏切換 (其它方向相同)
這種情況,在【General】-->【Device Orientation】中設置好支持的方向后,只需要在這些特殊的視圖控制器中重寫兩個方法:
// 支持設備自動旋轉
- (BOOL)shouldAutorotate {
return YES;
}
/**
* 設置特殊的界面支持的方向,這里特殊界面只支持Home在右側的情況
*/
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeRight;
}
(2)個別界面支持橫豎屏切換,其他所有界面都固定方向
可能大多數App會是這種需求,某些特殊界面只能橫屏,如視頻播放類App。
這里有兩種處理方式:
方式一:通過創建基類實現
在【General】-->【Device Orientation】中設置好需要支持的所有方向。然后使用一個基類控制器,在基類控制器中重寫兩個控制橫豎屏的方法:
// 支持設備自動旋轉
- (BOOL)shouldAutorotate {
return YES;
}
// 支持豎屏顯示
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
再然后,特殊的界面上再重寫這倆方法,讓其可以自動切換方向。
// 如果需要橫屏的時候,一定要重寫這個方法並返回NO
- (BOOL)prefersStatusBarHidden {
return NO;
}
// 支持設備自動旋轉
- (BOOL)shouldAutorotate {
return YES;
}
// 支持橫屏顯示
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// 如果該界面需要支持橫豎屏切換
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait;
// 如果該界面僅支持橫屏
// return UIInterfaceOrientationMaskLandscapeRight;
}
方式二 借助通知來控制界面的橫豎屏切換
用方式一的方法,還需要借助一個基類,所有的控制器都要繼承這個基類,感覺太麻煩。
還是整個App中大部分界面都是豎屏,某個界面可以橫豎屏切換的情況。
首先,在【General】-->【Device Orientation】設置僅支持豎屏,like this:
然后在特殊的視圖控制器里的ViewDidLoad中注冊通知:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
通知方法的實現過程:
// 用到的兩個宏:
#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)
- (void)deviceOrientationDidChange {
if([UIDevice currentDevice].orientation == UIDeviceOrientationPortrait) {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
[self orientationChange:NO];
//注意: UIDeviceOrientationLandscapeLeft 與 UIInterfaceOrientationLandscapeRight
} else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[self orientationChange:YES];
}
}
- (void)orientationChange:(BOOL)landscapeRight {
if (landscapeRight) {
[UIView animateWithDuration:0.2f animations:^{
self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}];
} else {
[UIView animateWithDuration:0.2f animations:^{
self.view.transform = CGAffineTransformMakeRotation(0);
self.view.bounds = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}];
}
}
最重要的一點:
需要重寫如下方法,並且返回NO。- (BOOL)shouldAutorotate { return NO; }這樣,在設備出於橫屏時,界面就會變成橫屏,設備處於豎屏時,界面就會變成豎屏。
三.橫豎屏控制優先級
對於限於VC范圍來講優先級最高的是當前的window的rootViewController,而往往我們的項目結構是容器視圖控制器控制VC,tabBarController控制navigationController之后是VC,而橫豎屏控制的優先級也是跟你的項目架構一樣。而且是一旦優先級高的關閉了橫豎屏配置,優先級低的無論如何配置都無法做到橫豎屏。所以在你接受這個需求的時候,你需要看一下根視圖的配置。
對於這種情況,我們有兩種處理方式,一種是通過模態的方式跳轉到下個VC,這個VC是隔離出來的,不在你之前的容器里,不會受到rootViewController的影響。
而另一種我們需要改造一下根視圖的配置:
-(BOOL)shouldAutorotate {
return [[self.viewControllers lastObject] shouldAutorotate];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
或者
-(BOOL)shouldAutorotate {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return YES;
}
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return UIInterfaceOrientationMaskLandscapeLeft;
}
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
if ([[self.viewControllers lastObject]isKindOfClass:[NewViewController class]]) {
return UIInterfaceOrientationLandscapeLeft;
}
return UIInterfaceOrientationPortrait;
}
可以看到我們通過獲取push棧中的最后一個VC的屬性,或指定特殊的VC來進行rootViewController的橫豎屏設置。
當然也可以通過NSNotificationCenter或者NSUserDefaults的方式對這里的值進行設置,在這里我就不過多贅述了。
總之要知道優先級的問題,general== appDelegate>> rootViewController>> nomalViewController
明白了權限的優先級以及開啟的方法我想轉屏就很顯而易見了。
四.注意事項和建議
1)注意事項
當我們的view controller隱藏的時候,設備方向也可能發生變化。例如view Controller A彈出一個全屏的view controller B的時候,由於A完全不可見,所以就接收不到屏幕旋轉消息。這個時候如果屏幕方向發生變化,再dismiss B的時候,A的方向就會不正確。我們可以通過在view controller A的viewWillAppear中更新方向來修正這個問題。
2)屏幕旋轉時的一些建議
- 在旋轉過程中,暫時界面操作的響應。
- 旋轉前后,盡量保持當前顯示的位置不變。
- 對於view層級比較復雜的時候,為了提高效率在旋轉開始前使用截圖替換當前的view層級,旋轉結束后再將原view層級替換回來。
- 在旋轉后最好強制reload tableview,保證在方向變化以后,新的row能夠充滿全屏。例如對於有些照片展示界面,豎屏只顯示一列,但是橫屏的時候顯示列表界面,這個時候一個界面就會顯示更多的元素,此時reload內容就是很有必要的。
