關于屏幕旋轉(zhuǎn)需要理解兩個概念設備方向(UIDeviceOrientation)和屏幕方向(UIInterfaceOrientation)
其中設備方向是物理方向,屏幕方向是APP內(nèi)容顯示方向,我們基本都是跟屏幕方向打交道,設備方向,我們只需要取當前設備方向值就OK了。
其中屏幕旋轉(zhuǎn)是建立在手機加速計基礎。
基礎知識
1. UIDeviceOrientation(設備的物理方向)
UIDeviceOrientation是我們手持的蘋果設備(iPhone,iPad..)的當前的朝向,是實物,共有七個方向,是以home鍵為基礎參照物的。
home鍵在左時,屏幕是向右旋轉(zhuǎn)(UIDeviceOrientationLandscapeRight),home鍵在右時,屏幕是向左旋轉(zhuǎn)(UIDeviceOrientationLandscapeLeft)。
當前屏幕的方向通過[UIDevice currentDevice].orientation方法獲取,這個方法我們只能讀取值,不能設置值,因為這是物理方向。
如果頁面的不支持自動旋轉(zhuǎn)功能我們獲取的值只能是UIDeviceOrientationPortrait
//Portrait 表示縱向,Landscape 表示橫向。
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
//未知方向,可能是設備(屏幕)斜置
UIDeviceOrientationUnknown,
//設備(屏幕)豎屏
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
//豎屏,只不過上下顛倒
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
//設備向左旋轉(zhuǎn)橫置
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
//設備向右旋轉(zhuǎn)橫置
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
//設備(屏幕)朝上平躺
UIDeviceOrientationFaceUp, // Device oriented flat, face up
//設備(屏幕)朝下平躺
UIDeviceOrientationFaceDown // Device oriented flat, face down
} __TVOS_PROHIBITED;
2. UIInterfaceOrientation(界面的顯示方向)
UIInterfaceOrientation界面的當前旋轉(zhuǎn)方向或者說是朝向(如果當前頁面支持屏幕旋轉(zhuǎn)就算設備旋轉(zhuǎn)鎖關閉了也可以強制屏幕旋轉(zhuǎn)),屏幕方向和設備方向的區(qū)別是一個是可以設置一個無能為力。
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
//屏幕方向未知
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
//向上正方向的豎屏
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
//向下正方向的豎屏
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
//向右旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
//向左旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
其中兩個枚舉值中的左右旋轉(zhuǎn)剛好對立,當設備向左轉(zhuǎn)時屏幕是向右轉(zhuǎn)的。
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
3.屏幕旋轉(zhuǎn)流程
加速計是屏幕旋轉(zhuǎn)的基礎,依賴加速計,設備才可以判斷出當前的設備方向。
當加速計檢測到方向變化的時候,會發(fā)出UIDeviceOrientationDidChangeNotification通知。
APP處理屏幕旋轉(zhuǎn)的流程
- 當設備加速計檢測到方向變化的時候,會發(fā)出
UIDeviceOrientationDidChangeNotification屏幕旋轉(zhuǎn)通知,這樣任何關心方向變化的View都可以通過注冊該通知,在設備方向變化的時候做出相應的響應。 - 設備旋轉(zhuǎn)的后,APP內(nèi)接收到旋轉(zhuǎn)事件(通知)。
- APP通過AppDelegate通知當前程序的KeyWindow。
- KeyWindow會知會它的rootViewController,判斷該View Controller所支持的旋轉(zhuǎn)方向,完成旋轉(zhuǎn)。
- 如果存在彈出的View Controller(模態(tài)彈的)的話,系統(tǒng)則會根據(jù)彈出的View Controller,來判斷是否要進行旋轉(zhuǎn)。
APP可以選擇性的(是否)接收 UIDeviceOrientationDidChangeNotification通知。
// 是否已經(jīng)開啟了設備方向改變的通知
@property(nonatomic,readonly,getter=isGeneratingDeviceOrientationNotifications)
BOOL generatesDeviceOrientationNotifications
__TVOS_PROHIBITED;
// 開啟接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)beginGeneratingDeviceOrientationNotifications
__TVOS_PROHIBITED; // nestable
// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)endGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED;
在 app 代理里面結(jié)束接收 設備旋轉(zhuǎn)的通知事件, 后續(xù)的屏幕旋轉(zhuǎn)都會失效
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
return YES;
}
UIViewController實現(xiàn)屏幕旋轉(zhuǎn)
在響應設備旋轉(zhuǎn)時,我們可以通過UIViewController的方法實現(xiàn)更細致控制,當View Controller接收到Window傳來的方向變化的時候,流程如下:
- 首先判斷當前ViewController是否支持旋轉(zhuǎn)到目標方向,如果支持的話進入流程2,否則此次旋轉(zhuǎn)流程直接結(jié)束。
- 調(diào)用 willRotateToInterfaceOrientation:duration: 方法,通知View Controller將要旋轉(zhuǎn)到目標方向。如果該ViewController是一個Container View Controller的話,它會繼續(xù)調(diào)用其Content View Controller的該方法。這個時候我們也可以暫時將一些View隱藏掉,等旋轉(zhuǎn)結(jié)束以后在現(xiàn)實出來。
- Window調(diào)整顯示的View Controller的bounds,由于View Controller的bounds發(fā)生變化,將會觸發(fā) viewWillLayoutSubviews 方法。這個時候self.interfaceOrientation和statusBarOrientation方向還是原來的方向。
- 接著當前View Controller的 willAnimateRotationToInterfaceOrientation:duration: 方法將會被調(diào)用。系統(tǒng)將會把該方法中執(zhí)行的所有屬性變化放到動animation block中。
- 執(zhí)行方向旋轉(zhuǎn)的動畫。
- 最后調(diào)用 didRotateFromInterfaceOrientation: 方法,通知View Controller旋轉(zhuǎn)動畫執(zhí)行完畢。這個時候我們可以將第二部隱藏的View再顯示出來。
響應過程如下圖所示:

4. 監(jiān)聽屏幕旋轉(zhuǎn)方向
UIDeviceOrientationDidChangeNotification
//添加監(jiān)聽設備方向的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil];
//監(jiān)聽設備方向的通知方法
#pragma mark - 監(jiān)聽設備方向和全屏
//參照坐標 手機在豎屏情況下
- (void)onDeviceOrientationChange{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:{
NSLog(@"狀態(tài)欄在手機下方 不過一般不會用到");
}
break;
case UIInterfaceOrientationPortrait:{
NSLog(@"手機在豎屏狀態(tài)下");
}
break;
case UIInterfaceOrientationLandscapeLeft:{
NSLog(@"狀態(tài)欄在手機左側(cè)");
}
break;
case UIInterfaceOrientationLandscapeRight:{
NSLog(@"狀態(tài)欄在手機右側(cè)");
}
break;
default:
break;
}
}
界面發(fā)生變化狀態(tài)欄改變通知
UIApplicationWillChangeStatusBarOrientationNotification
UIApplicationDidChangeStatusBarOrientationNotification
//以監(jiān)聽UIApplicationDidChangeStatusBarOrientationNotification通知為例
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleStatusBarOrientationChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
//界面方向改變的處理
- (void)handleStatusBarOrientationChange: (NSNotification *)notification{
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
switch (interfaceOrientation) {
case UIInterfaceOrientationUnknown:
NSLog(@"未知方向");
break;
case UIInterfaceOrientationPortrait:
NSLog(@"界面直立");
break;
case UIInterfaceOrientationPortraitUpsideDown:
NSLog(@"界面直立,上下顛倒");
break;
case UIInterfaceOrientationLandscapeLeft:
NSLog(@"界面朝左");
break;
case UIInterfaceOrientationLandscapeRight:
NSLog(@"界面朝右");
break;
default:
break;
}
}
- (void)dealloc{
//最后在dealloc中移除通知
[[NSNotificationCenter defaultCenter]removeObserver:self];
[[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
}
PS:手機鎖定豎屏后,UIApplicationWillChangeStatusBarOrientationNotification,UIApplicationDidChangeStatusBarOrientationNotification和UIDeviceOrientationDidChangeNotification通知都會失效。
具體應用
前提:想要APP支持橫豎屏或者某個頁面或者功能可以橫豎屏切換,前提是項目支持橫豎屏不然強制橫屏也沒用,需要我們的TARGETS中或者info.plist文件中右或者appdelegate中設置。我們在項目中屏幕旋轉(zhuǎn)的方向就是這些設置的屏幕支持的方向,如果超出項目設置好的屏幕朝向APP會crash。
project > TARGETS > Gengral > Deployment Info > Device Orientation



項目中是否可以旋轉(zhuǎn)主要由下面這三個方法控制
- (BOOL) shouldAutorotate;
- (UIInterfaceOrientationMask) supportedInterfaceOrientations;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
一. shouldAutorotate(是否支持自動旋轉(zhuǎn))
- (BOOL) shouldAutorotate{
return YES;
}
shouldAutorotate作用是調(diào)用這個方法的控制器是否支持自動旋轉(zhuǎn),使用這個方法前提是需要項目支持橫屏,返回值是BOOL。
情景:
- NO 當前頁面不可以自動橫屏,手機豎屏鎖在打開或者關閉,調(diào)用強制橫屏方法無用,不可以橫屏。
//手機強制橫屏方法
NSNumber *orientation = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setValue:orientation forKey:@"orientation"];
- YES 當前頁面支持自動旋轉(zhuǎn),手機豎屏鎖打開,不可以根據(jù)手機方向旋轉(zhuǎn),調(diào)用強制橫屏方法頁面可以橫屏。
- YES 手機豎屏鎖關閉,屏幕可以根據(jù)手機朝向旋轉(zhuǎn),也可以強制旋轉(zhuǎn)。
二. supportedInterfaceOrientations(當前屏幕支持的方向)
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
supportedInterfaceOrientations作用是屏幕支持的方向有哪些,這個方法返回值是個枚舉值
UIInterfaceOrientationMask具體值有下面
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
//向上為正方向的豎屏
UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
//向左移旋轉(zhuǎn)的橫屏
UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
//向右旋轉(zhuǎn)的橫屏
UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
//向下為正方向的豎屏
UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
//向左或者向右的橫屏
UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
//所有的橫豎屏方向都支持
UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
//支持向上的豎屏和左右方向的橫屏
UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} __TVOS_PROHIBITED;
三. preferredInterfaceOrientationForPresentation
preferredInterfaceOrientationForPresentation 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)返回值是UIInterfaceOrientation是個枚舉值
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
//屏幕方向未知
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
//向上正方向的豎屏
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
//向下正方向的豎屏
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
//向右旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
//向左旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
四. 具體實現(xiàn)
以上三個方法如果是在隨便寫的一個demo中完全OK,但是要是在完整項目中,會無效。
原因:
測試結(jié)果是當前控制器(頁面)是否支持旋轉(zhuǎn)是由根視圖控制器控制的也就是rootViewController,跟視圖控制器如果沒有重寫上面三個方法默認是支持自動旋轉(zhuǎn)的。因為隨便寫的demo里面的ViewControl就是根視圖控制器所以有效。
一般情況下我們的根視圖控制器要么是navigationController要么是tabbarController也有ViewControl。所以我們在根視圖控制器下重寫上面三個方法,把是否支持橫豎屏給需要橫豎屏的頁面控制器來控制,或者寫一個category。
下面是navigationController重寫的方法其他的一樣
1. 導航根視圖控制器
導航根視圖控制器下重寫方法:
#import "HPNavigationController.h"
@implementation HPNavigationController
- (BOOL) shouldAutorotate{
NSLog(@"%@",[UIApplication sharedApplication].keyWindow.rootViewController);
return [self.visibleViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
return [self.visibleViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.visibleViewController preferredInterfaceOrientationForPresentation];
}
@end
category重寫方法:
#import "UINavigationController+HPToolBar.h"
@implementation UINavigationController (HPToolBar)
- (BOOL) shouldAutorotate{
return [self.topViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation) preferredInterfaceOrientationForPresentation{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
- (UIViewController *)childViewControllerForStatusBarStyle{
return self.topViewController;
}
- (UIViewController *)childViewControllerForStatusBarHidden{
return self.topViewController;
}
@end
1. tabBar根視圖控制器
tabBar根視圖控制器下重寫方法:
#import "HPUITabBarController.h"
@implementation HPUITabBarController
- (BOOL) shouldAutorotate{
return [self.selectedViewController shouldAutorotate];
}
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
return [self.selectedViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
@end
category重寫方法:
#import "UITabBarController+HPToolBar.h"
@implementation UITabBarController (HPToolBar)
// 是否支持自動轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController shouldAutorotate];
} else {
return [vc shouldAutorotate];
}
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController supportedInterfaceOrientations];
} else {
return [vc supportedInterfaceOrientations];
}
}
// 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
UIViewController *vc = self.viewControllers[self.selectedIndex];
if ([vc isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)vc;
return [nav.topViewController preferredInterfaceOrientationForPresentation];
} else {
return [vc preferredInterfaceOrientationForPresentation];
}
}
@end
ViewController根視圖控制器 category重寫方法
#import "UIViewController+HPToolBar.h"
@implementation UIViewController (HPToolBar)
// 是否支持自動轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
return NO;
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
// 默認的屏幕方向(當前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導航的無效)方式展現(xiàn)出來的,才會調(diào)用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
@end
寫過這三個方法后就可以在需要旋轉(zhuǎn)的控制器下重寫這三個方法,可以實現(xiàn)自動旋轉(zhuǎn)或者強制旋轉(zhuǎn),不過強制旋轉(zhuǎn)的前提是shouldAutorotate這個方法的返回值為YES不然強制旋轉(zhuǎn)無效。上面的執(zhí)行順序是先找根視圖下的方法,如果有會直接回調(diào)。如果沒有會找有沒有navigationController的類別,有就回調(diào),沒有就直接默認為YES。如果根視圖控制器沒有這重寫這三個方法,會找當前ViewControll繼承的視圖父類或者類別。在把橫屏配置打開情況下如果沒有重寫這三個方法頁面只支持豎屏(實測)。如果想要某個頁面支持旋轉(zhuǎn)只需要在支持旋轉(zhuǎn)的控制下重寫這三個方法。
有關shouldAutorotate調(diào)用沒反應的問題上面有解釋,是因為視圖是否旋轉(zhuǎn)是由根視圖控制器控制,只要重寫了上面的方法,就沒問題。