關(guān)于屏幕旋轉(zhuǎn)需要理解兩個(gè)概念設(shè)備方向(UIDeviceOrientation)和屏幕方向(UIInterfaceOrientation)
其中設(shè)備方向是物理方向翁都,屏幕方向是APP內(nèi)容顯示方向绎晃,我們基本都是跟屏幕方向打交道,設(shè)備方向示弓,我們只需要取當(dāng)前設(shè)備方向值就OK了径缅。
其中屏幕旋轉(zhuǎn)是建立在手機(jī)加速計(jì)基礎(chǔ)髓帽。
基礎(chǔ)知識(shí)
1. UIDeviceOrientation(設(shè)備的物理方向)
UIDeviceOrientation
是我們手持的蘋果設(shè)備(iPhone,iPad..)的當(dāng)前的朝向婚肆,是實(shí)物,共有七個(gè)方向妓美,是以home鍵為基礎(chǔ)參照物的僵腺。
home鍵在左時(shí),屏幕是向右旋轉(zhuǎn)(UIDeviceOrientationLandscapeRight)
壶栋,home鍵在右時(shí)辰如,屏幕是向左旋轉(zhuǎn)(UIDeviceOrientationLandscapeLeft)
。
當(dāng)前屏幕的方向通過[UIDevice currentDevice].orientation
方法獲取贵试,這個(gè)方法我們只能讀取值琉兜,不能設(shè)置值,因?yàn)檫@是物理方向毙玻。
如果頁面的不支持自動(dòng)旋轉(zhuǎn)功能我們獲取的值只能是UIDeviceOrientationPortrait
//Portrait 表示縱向豌蟋,Landscape 表示橫向。
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
//未知方向桑滩,可能是設(shè)備(屏幕)斜置
UIDeviceOrientationUnknown,
//設(shè)備(屏幕)豎屏
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
//豎屏梧疲,只不過上下顛倒
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
//設(shè)備向左旋轉(zhuǎn)橫置
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
//設(shè)備向右旋轉(zhuǎn)橫置
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
//設(shè)備(屏幕)朝上平躺
UIDeviceOrientationFaceUp, // Device oriented flat, face up
//設(shè)備(屏幕)朝下平躺
UIDeviceOrientationFaceDown // Device oriented flat, face down
} __TVOS_PROHIBITED;
2. UIInterfaceOrientation(界面的顯示方向)
UIInterfaceOrientation
界面的當(dāng)前旋轉(zhuǎn)方向或者說是朝向(如果當(dāng)前頁面支持屏幕旋轉(zhuǎn)就算設(shè)備旋轉(zhuǎn)鎖關(guān)閉了也可以強(qiáng)制屏幕旋轉(zhuǎn)),屏幕方向和設(shè)備方向的區(qū)別是一個(gè)是可以設(shè)置一個(gè)無能為力运准。
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
//屏幕方向未知
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
//向上正方向的豎屏
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
//向下正方向的豎屏
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
//向右旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
//向左旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
其中兩個(gè)枚舉值中的左右旋轉(zhuǎn)剛好對(duì)立幌氮,當(dāng)設(shè)備向左轉(zhuǎn)時(shí)屏幕是向右轉(zhuǎn)的。
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
3.屏幕旋轉(zhuǎn)流程
加速計(jì)是屏幕旋轉(zhuǎn)的基礎(chǔ),依賴加速計(jì)胁澳,設(shè)備才可以判斷出當(dāng)前的設(shè)備方向该互。
當(dāng)加速計(jì)檢測(cè)到方向變化的時(shí)候,會(huì)發(fā)出UIDeviceOrientationDidChangeNotification
通知韭畸。
APP處理屏幕旋轉(zhuǎn)的流程
- 當(dāng)設(shè)備加速計(jì)檢測(cè)到方向變化的時(shí)候宇智,會(huì)發(fā)出
UIDeviceOrientationDidChangeNotification
屏幕旋轉(zhuǎn)通知蔓搞,這樣任何關(guān)心方向變化的View都可以通過注冊(cè)該通知,在設(shè)備方向變化的時(shí)候做出相應(yīng)的響應(yīng)随橘。 - 設(shè)備旋轉(zhuǎn)的后败明,APP內(nèi)接收到旋轉(zhuǎn)事件(通知)。
- APP通過AppDelegate通知當(dāng)前程序的KeyWindow太防。
- KeyWindow會(huì)知會(huì)它的rootViewController,判斷該View Controller所支持的旋轉(zhuǎn)方向酸员,完成旋轉(zhuǎn)蜒车。
- 如果存在彈出的View Controller(模態(tài)彈的)的話,系統(tǒng)則會(huì)根據(jù)彈出的View Controller幔嗦,來判斷是否要進(jìn)行旋轉(zhuǎn)酿愧。
APP可以選擇性的(是否)接收 UIDeviceOrientationDidChangeNotification
通知。
// 是否已經(jīng)開啟了設(shè)備方向改變的通知
@property(nonatomic,readonly,getter=isGeneratingDeviceOrientationNotifications)
BOOL generatesDeviceOrientationNotifications
__TVOS_PROHIBITED;
// 開啟接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)beginGeneratingDeviceOrientationNotifications
__TVOS_PROHIBITED; // nestable
// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)endGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED;
在 app 代理里面結(jié)束接收 設(shè)備旋轉(zhuǎn)的通知事件, 后續(xù)的屏幕旋轉(zhuǎn)都會(huì)失效
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
return YES;
}
UIViewController實(shí)現(xiàn)屏幕旋轉(zhuǎn)
在響應(yīng)設(shè)備旋轉(zhuǎn)時(shí)邀泉,我們可以通過UIViewController的方法實(shí)現(xiàn)更細(xì)致控制嬉挡,當(dāng)View Controller接收到Window傳來的方向變化的時(shí)候,流程如下:
- 首先判斷當(dāng)前ViewController是否支持旋轉(zhuǎn)到目標(biāo)方向汇恤,如果支持的話進(jìn)入流程2庞钢,否則此次旋轉(zhuǎn)流程直接結(jié)束。
- 調(diào)用 willRotateToInterfaceOrientation:duration: 方法因谎,通知View Controller將要旋轉(zhuǎn)到目標(biāo)方向基括。如果該ViewController是一個(gè)Container View Controller的話,它會(huì)繼續(xù)調(diào)用其Content View Controller的該方法财岔。這個(gè)時(shí)候我們也可以暫時(shí)將一些View隱藏掉风皿,等旋轉(zhuǎn)結(jié)束以后在現(xiàn)實(shí)出來。
- Window調(diào)整顯示的View Controller的bounds匠璧,由于View Controller的bounds發(fā)生變化桐款,將會(huì)觸發(fā) viewWillLayoutSubviews 方法。這個(gè)時(shí)候self.interfaceOrientation和statusBarOrientation方向還是原來的方向夷恍。
- 接著當(dāng)前View Controller的 willAnimateRotationToInterfaceOrientation:duration: 方法將會(huì)被調(diào)用魔眨。系統(tǒng)將會(huì)把該方法中執(zhí)行的所有屬性變化放到動(dòng)animation block中。
- 執(zhí)行方向旋轉(zhuǎn)的動(dòng)畫酿雪。
- 最后調(diào)用 didRotateFromInterfaceOrientation: 方法冰沙,通知View Controller旋轉(zhuǎn)動(dòng)畫執(zhí)行完畢。這個(gè)時(shí)候我們可以將第二部隱藏的View再顯示出來执虹。
響應(yīng)過程如下圖所示:
4. 監(jiān)聽屏幕旋轉(zhuǎn)方向
UIDeviceOrientationDidChangeNotification
//添加監(jiān)聽設(shè)備方向的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationChange) name:UIDeviceOrientationDidChangeNotification object:nil];
//監(jiān)聽設(shè)備方向的通知方法
#pragma mark - 監(jiān)聽設(shè)備方向和全屏
//參照坐標(biāo) 手機(jī)在豎屏情況下
- (void)onDeviceOrientationChange{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:{
NSLog(@"狀態(tài)欄在手機(jī)下方 不過一般不會(huì)用到");
}
break;
case UIInterfaceOrientationPortrait:{
NSLog(@"手機(jī)在豎屏狀態(tài)下");
}
break;
case UIInterfaceOrientationLandscapeLeft:{
NSLog(@"狀態(tài)欄在手機(jī)左側(cè)");
}
break;
case UIInterfaceOrientationLandscapeRight:{
NSLog(@"狀態(tài)欄在手機(jī)右側(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:手機(jī)鎖定豎屏后,UIApplicationWillChangeStatusBarOrientationNotification
,UIApplicationDidChangeStatusBarOrientationNotification
和UIDeviceOrientationDidChangeNotification
通知都會(huì)失效袋励。
具體應(yīng)用
前提:想要APP支持橫豎屏或者某個(gè)頁面或者功能可以橫豎屏切換侥啤,前提是項(xiàng)目支持橫豎屏不然強(qiáng)制橫屏也沒用当叭,需要我們的TARGETS中或者info.plist文件中右或者appdelegate中設(shè)置。我們?cè)陧?xiàng)目中屏幕旋轉(zhuǎn)的方向就是這些設(shè)置的屏幕支持的方向盖灸,如果超出項(xiàng)目設(shè)置好的屏幕朝向APP會(huì)crash蚁鳖。
project > TARGETS > Gengral > Deployment Info > Device Orientation
項(xiàng)目中是否可以旋轉(zhuǎn)主要由下面這三個(gè)方法控制
- (BOOL) shouldAutorotate;
- (UIInterfaceOrientationMask) supportedInterfaceOrientations;
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;
一. shouldAutorotate(是否支持自動(dòng)旋轉(zhuǎn))
- (BOOL) shouldAutorotate{
return YES;
}
shouldAutorotate作用是調(diào)用這個(gè)方法的控制器是否支持自動(dòng)旋轉(zhuǎn),使用這個(gè)方法前提是需要項(xiàng)目支持橫屏,返回值是BOOL赁炎。
情景:
- NO 當(dāng)前頁面不可以自動(dòng)橫屏醉箕,手機(jī)豎屏鎖在打開或者關(guān)閉,調(diào)用強(qiáng)制橫屏方法無用,不可以橫屏徙垫。
//手機(jī)強(qiáng)制橫屏方法
NSNumber *orientation = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setValue:orientation forKey:@"orientation"];
- YES 當(dāng)前頁面支持自動(dòng)旋轉(zhuǎn)讥裤,手機(jī)豎屏鎖打開,不可以根據(jù)手機(jī)方向旋轉(zhuǎn)姻报,調(diào)用強(qiáng)制橫屏方法頁面可以橫屏己英。
- YES 手機(jī)豎屏鎖關(guān)閉,屏幕可以根據(jù)手機(jī)朝向旋轉(zhuǎn)吴旋,也可以強(qiáng)制旋轉(zhuǎn)损肛。
二. supportedInterfaceOrientations(當(dāng)前屏幕支持的方向)
- (UIInterfaceOrientationMask) supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
supportedInterfaceOrientations作用是屏幕支持的方向有哪些,這個(gè)方法返回值是個(gè)枚舉值
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 默認(rèn)的屏幕方向(當(dāng)前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導(dǎo)航的無效)方式展現(xiàn)出來的荣瑟,才會(huì)調(diào)用這個(gè)方法)返回值是UIInterfaceOrientation是個(gè)枚舉值
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
//屏幕方向未知
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
//向上正方向的豎屏
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
//向下正方向的豎屏
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
//向右旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
//向左旋轉(zhuǎn)的橫屏
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
四. 具體實(shí)現(xiàn)
以上三個(gè)方法如果是在隨便寫的一個(gè)demo中完全OK治拿,但是要是在完整項(xiàng)目中,會(huì)無效笆焰。
原因:
測(cè)試結(jié)果是當(dāng)前控制器(頁面)是否支持旋轉(zhuǎn)是由根視圖控制器控制的也就是rootViewController
忍啤,跟視圖控制器如果沒有重寫上面三個(gè)方法默認(rèn)是支持自動(dòng)旋轉(zhuǎn)的。因?yàn)殡S便寫的demo里面的ViewControl就是根視圖控制器所以有效仙辟。
一般情況下我們的根視圖控制器要么是navigationController要么是tabbarController也有ViewControl同波。所以我們?cè)诟晥D控制器下重寫上面三個(gè)方法,把是否支持橫豎屏給需要橫豎屏的頁面控制器來控制,或者寫一個(gè)category叠国。
下面是navigationController重寫的方法其他的一樣
1. 導(dǎo)航根視圖控制器
導(dǎo)航根視圖控制器下重寫方法:
#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)
// 是否支持自動(dòng)轉(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];
}
}
// 默認(rèn)的屏幕方向(當(dāng)前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導(dǎo)航的無效)方式展現(xiàn)出來的未檩,才會(huì)調(diào)用這個(gè)方法)
- (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)
// 是否支持自動(dòng)轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
return NO;
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
// 默認(rèn)的屏幕方向(當(dāng)前ViewController必須是通過模態(tài)出來的UIViewController(模態(tài)帶導(dǎo)航的無效)方式展現(xiàn)出來的,才會(huì)調(diào)用這個(gè)方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
@end
寫過這三個(gè)方法后就可以在需要旋轉(zhuǎn)的控制器下重寫這三個(gè)方法粟焊,可以實(shí)現(xiàn)自動(dòng)旋轉(zhuǎn)或者強(qiáng)制旋轉(zhuǎn),不過強(qiáng)制旋轉(zhuǎn)的前提是shouldAutorotate
這個(gè)方法的返回值為YES
不然強(qiáng)制旋轉(zhuǎn)無效冤狡。上面的執(zhí)行順序是先找根視圖下的方法,如果有會(huì)直接回調(diào)项棠。如果沒有會(huì)找有沒有navigationController的類別悲雳,有就回調(diào),沒有就直接默認(rèn)為YES香追。如果根視圖控制器沒有這重寫這三個(gè)方法合瓢,會(huì)找當(dāng)前ViewControll繼承的視圖父類或者類別。在把橫屏配置打開情況下如果沒有重寫這三個(gè)方法頁面只支持豎屏(實(shí)測(cè))透典。如果想要某個(gè)頁面支持旋轉(zhuǎn)只需要在支持旋轉(zhuǎn)的控制下重寫這三個(gè)方法晴楔。
有關(guān)shouldAutorotate
調(diào)用沒反應(yīng)的問題上面有解釋顿苇,是因?yàn)橐晥D是否旋轉(zhuǎn)是由根視圖控制器控制,只要重寫了上面的方法税弃,就沒問題纪岁。