關(guān)于橫豎屏適配,有一句說(shuō)一句,坑挺深的。之前做Vision和畢設(shè)的時(shí)候就處理過(guò)橫豎屏問(wèn)題,不過(guò)當(dāng)時(shí)的功力太淺,明顯沒有處理明白。所以這次在公司項(xiàng)目中又一次遇到了這種橫豎屏的需求,自然要認(rèn)真的搞一哈,順便總結(jié)一下分享給大家。其實(shí)在我理解上,只要明白以下幾點(diǎn),橫豎屏處理上并不是問(wèn)題。大家按需跳轉(zhuǎn)吧:
1.橫豎屏方向枚舉
關(guān)于橫豎屏一共有三種枚舉,UIInterfaceOrientation,UIInterfaceOrientationMask,UIDeviceOrientation。
1.1 UIInterfaceOrientation與UIDeviceOrientation
為什么這兩個(gè)放在一起說(shuō),好吧,你看看下面這個(gè)枚舉定義:
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
}
我相信你應(yīng)該看出了點(diǎn)東西了把,對(duì)于iOS
設(shè)備來(lái)講,屏幕狀態(tài)由以上五種狀態(tài)。上下翻轉(zhuǎn)還是很好區(qū)分的,左右旋轉(zhuǎn)可能就不是很好區(qū)分。
這里有個(gè)坑?。。?/strong>
高能預(yù)警:
請(qǐng)仔細(xì)觀察上面的枚舉值。
在處于豎屏和上下翻轉(zhuǎn)的狀態(tài)下這兩個(gè)枚舉值是一樣的,而當(dāng)處于橫屏?xí)r,這兩個(gè)值剛好相反。
所以在有時(shí)你發(fā)現(xiàn)跟你預(yù)期的翻轉(zhuǎn)方向不一樣的時(shí)候,可能你用錯(cuò)了枚舉。
UIDeviceOrientation
是設(shè)備的當(dāng)前所處的方向,而且事實(shí)上它有6個(gè)值,
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
UIDeviceOrientationFaceUp, // Device oriented flat, face up
UIDeviceOrientationFaceDown // Device oriented flat, face down
}
分別對(duì)應(yīng)iPhone
未知方向,豎直,上下反轉(zhuǎn),向左旋轉(zhuǎn),向右旋轉(zhuǎn),屏幕朝上,屏幕朝下。關(guān)于橫屏如何去分左右,其實(shí)API中的注釋已經(jīng)說(shuō)明,當(dāng)處于UIDeviceOrientationLandscapeLeft
,home鍵在右側(cè),當(dāng)處于UIDeviceOrientationLandscapeRight
,home鍵在左側(cè)。
所以,UIDevice
顧名思義,事實(shí)上是用來(lái)判斷設(shè)備方向的。
UIInterfaceOrientation
即當(dāng)前頁(yè)面的方向。
在設(shè)備進(jìn)行橫屏旋轉(zhuǎn)的時(shí)候,為了橫屏?xí)r上下不翻轉(zhuǎn),所以當(dāng)Device處于Left時(shí),界面應(yīng)該是Right旋轉(zhuǎn)。這樣才不會(huì)使橫屏?xí)r內(nèi)容上下翻轉(zhuǎn)。所以我想你應(yīng)該明白了為什么在處于橫屏?xí)r為什么他們倆的值是剛好相反的。
所以對(duì)于橫豎屏適配,使用的枚舉大家一定要看好,使用UIInterfaceOrientation
。不要搞反。
1.2 UIInterfaceOrientationMask
其實(shí)蘋果大大還是給了我們更清晰和方便的枚舉如下:
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),
}
事實(shí)上我們?cè)跈M豎屏適配時(shí),最常用的是這個(gè)枚舉。這個(gè)枚舉詳細(xì)的列舉了各種你需要的情況。我就不贅述了。官方的命名還是很舒服很好理解的。
2.開啟橫豎屏權(quán)限
開啟橫豎屏的方式有兩種,一種是在項(xiàng)目中直接進(jìn)行勾選,
可以看到這種勾選方式允許你進(jìn)行四個(gè)方向的配置,并且這種勾選方式會(huì)直接在你的項(xiàng)目plist文件中添加
但是由于在這里配置是對(duì)項(xiàng)目啟動(dòng)時(shí)lanuch界面產(chǎn)生影響,而往往我們又沒有對(duì)lanuch進(jìn)行橫豎屏適配,所以在這個(gè)時(shí)候我們就需要使用第二種方式進(jìn)行配置。
在項(xiàng)目中的AppDelegate文件中進(jìn)行配置。
#pragma mark - InterfaceOrientation //應(yīng)用支持的方向
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
搭配UIInterfaceOrientationMask
使用,你可以很方便的讓你項(xiàng)目開啟你所需要的橫豎屏權(quán)限和限制條件。
3.在VC中如何控制橫豎屏
我們都知道MVC架構(gòu),那么顯而易見,在我們開啟了項(xiàng)目的橫豎屏的限制之后,需要在ViewController
進(jìn)行相應(yīng)的配置,才能真正實(shí)現(xiàn)橫豎屏。
開啟橫豎屏,我們需要在VC中添加如下代碼:
// 設(shè)備支持方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}
// 默認(rèn)方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait; // 或者其他值 balabala~
}
而對(duì)于橫豎屏,手機(jī)端一般有兩種情況,一種是手機(jī)沒有開啟橫豎屏鎖定,用戶將手機(jī)橫屏?xí)r觸發(fā)的。對(duì)于第一種情況,我們只需要在VC中添加:
// 開啟自動(dòng)轉(zhuǎn)屏
- (BOOL)shouldAutorotate {
return YES;
}
另一種是我們?cè)陧?xiàng)目中的某些條件下強(qiáng)行讓屏幕橫屏,例如大圖預(yù)覽,視頻播放,等等。而對(duì)于這種情況,我們可以使用下面??這兩種方法,都可以實(shí)現(xiàn)效果:
- (void)setInterfaceOrientation:(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];
}
}
- (void)setInterfaceOrientation:(UIDeviceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:orientation] forKey:@"orientation"];
}
}
PS:這兩個(gè)方法只有在- (BOOL)shouldAutorotate( return YES; )
時(shí),才會(huì)生效。并且請(qǐng)注意使用的枚舉值的不同。
4.橫豎屏控制優(yōu)先級(jí)
在我們接手一個(gè)項(xiàng)目后,說(shuō)要添加一個(gè)某個(gè)界面橫豎屏需求時(shí),發(fā)現(xiàn)按照上面的方式配置了一圈,發(fā)現(xiàn)還是轉(zhuǎn)!不!成!功!What F***?。。?/p>
事實(shí)上在這里我們要了解一個(gè)問(wèn)題,就是關(guān)于橫豎屏控制的優(yōu)先級(jí)。對(duì)于限于VC
范圍來(lái)講優(yōu)先級(jí)最高的是當(dāng)前的window
的rootViewController
,而往往我們的項(xiàng)目結(jié)構(gòu)是容器視圖控制器控制VC
,tabBarController
控制navigationController
之后是VC
,而橫豎屏控制的優(yōu)先級(jí)也是跟你的項(xiàng)目架構(gòu)一樣。而且是一旦優(yōu)先級(jí)高的關(guān)閉了橫豎屏配置,優(yōu)先級(jí)低的無(wú)論如何配置都無(wú)法做到橫豎屏。所以在你接受這個(gè)需求的時(shí)候,你需要看一下根視圖的配置。
對(duì)于這種情況,我們有兩種處理方式,一種是通過(guò)模態(tài)的方式跳轉(zhuǎn)的下個(gè)VC
,這個(gè)VC
是隔離出來(lái)的,不在你之前的容器里,不會(huì)受到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;
}
可以看到我們通過(guò)獲取push棧中的最后一個(gè)VC
的屬性,或指定特殊的VC
來(lái)進(jìn)行rootViewController
的橫豎屏設(shè)置。
當(dāng)然也可以通過(guò)NSNotificationCenter
或者NSUserDefaults
的方式對(duì)這里的值進(jìn)行設(shè)置,在這里我就不過(guò)多贅述了。
總之要知道優(yōu)先級(jí)的問(wèn)題,general
== appDelegate
>> rootViewController
>> nomalViewController
明白了權(quán)限的優(yōu)先級(jí)以及開啟的方法我想轉(zhuǎn)屏就很顯而易見了。
5.橫豎屏適配
事實(shí)上旋轉(zhuǎn)屏幕成功,對(duì)于iOS橫豎屏問(wèn)題我們只是完成了一半。另一半就是UI適配問(wèn)題,其實(shí)這個(gè)要說(shuō)起來(lái)就比較麻煩了,有些時(shí)候有很多case需要針對(duì)對(duì)應(yīng)的業(yè)務(wù)條件來(lái)定制。但是無(wú)外乎幾種實(shí)現(xiàn)思路。這里博主給大家拋幾塊磚哈:
首先我們要知道,當(dāng)發(fā)生轉(zhuǎn)屏事件時(shí),系統(tǒng)的回調(diào)方法是:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
if (size.width > size.height) { // 橫屏
// 橫屏布局 balabala
} else {
// 豎屏布局 balabala
}
}
首推的方式是使用約束布局,在使用約束布局時(shí),橫豎屏轉(zhuǎn)換時(shí),在通常情況下約束條件會(huì)很相似,所以在布局上會(huì)極大的減少代碼量。其次如果有個(gè)別的特殊問(wèn)題,可以在上面的回調(diào)方法里面進(jìn)行微調(diào)。
其次,對(duì)于轉(zhuǎn)屏后,[UIScreen mainScreen].bounds.size
以及self.view.frame.size
的寬高系統(tǒng)會(huì)自動(dòng)調(diào)換。即在橫屏的時(shí)候width > height
。所以在我們進(jìn)行常規(guī)布局的時(shí)候我們可以選擇控件的frame
屬性都與這兩個(gè)屬性進(jìn)行比例換算。這樣在當(dāng)橫豎屏轉(zhuǎn)換的時(shí)候,重布局時(shí),也會(huì)適應(yīng)成對(duì)應(yīng)屏幕下的布局。同樣有需要特殊處理的布局,在上面的回調(diào)方法中進(jìn)行細(xì)節(jié)微調(diào)即可。
對(duì)于子視圖,在橫豎屏切換時(shí),會(huì)觸發(fā)子視圖重布局的方法:
- (void)layoutSubviews {
[super layoutSubviews];
// 通過(guò)狀態(tài)欄電池圖標(biāo)來(lái)判斷屏幕方向
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationMaskPortrait) {
// 豎屏 balabala
} else {
// 橫屏 balabala
}
}
當(dāng)然我只是說(shuō)了幾種比較簡(jiǎn)單的處理方式,和應(yīng)對(duì)方法,對(duì)于整個(gè)項(xiàng)目都需要橫豎屏適配的,我想還是一個(gè)比較復(fù)雜的過(guò)程。在實(shí)在處理不了的問(wèn)題上,也可以通過(guò)寫兩套布局的方式來(lái)處理。至于過(guò)場(chǎng)動(dòng)畫,理論上如果你用約束和我說(shuō)的比例布局的方式來(lái)寫,基本系統(tǒng)會(huì)自動(dòng)幫你這個(gè)問(wèn)題給處理掉。但如果兩種布局差距很大,你用了兩套完全不同的布局,那這個(gè)你可能就要傷腦筋了。哈哈哈。不過(guò)有一些情況處理要求不嚴(yán)格的話可以使用截圖過(guò)場(chǎng)大法來(lái)解決。不過(guò)這個(gè)就不是本文的設(shè)計(jì)范圍了。大家如果感興趣可以自己google一下。當(dāng)然我日后的文章也可能會(huì)寫到這了。到時(shí)候再來(lái)這里修改。
6.總結(jié)
iOS橫豎屏適配,確實(shí)有很多坑,當(dāng)然,有些坑是系統(tǒng)的;而有些坑,是因?yàn)槲覀兊臒o(wú)知而造成的。所以多看多學(xué)多做多理解,必然能讓你學(xué)到更多,增強(qiáng)填坑的硬實(shí)力。而對(duì)于橫豎屏適配這塊,轉(zhuǎn)屏并不難,難的是橫豎屏布局適配。博主只是簡(jiǎn)單的說(shuō)了一些思路,至于實(shí)現(xiàn)起來(lái),還是要針對(duì)對(duì)應(yīng)的需求來(lái)進(jìn)行處理。下面??給大家鏈接幾個(gè)博主覺得還不錯(cuò)的關(guān)于適配的文章,大家可以看看: