UIDeviceOrientation與UIinterfaceOrientation以及屏幕旋轉(zhuǎn)的方式
在日常開發(fā)過程中,經(jīng)常會遇到轉(zhuǎn)屏的需求,最近遇到一個轉(zhuǎn)屏相關(guān)的bug,順帶著總結(jié)下iOS端實現(xiàn)轉(zhuǎn)屏需要做的事情。
什么是設(shè)備方向,什么是視圖方向
首先需要明確兩個概念,設(shè)備方向(UIDeviceOrientation)和視圖方向(UIInterfaceOrientation)
UIDeviceOrientation:
不受鎖定屏幕方向的影響,通過
[UIDevice currentDevice].orientation
客觀反映出當(dāng)前設(shè)備所處的方向,與視野中正在展示視圖的方向無關(guān),該屬性只讀,無法修改
該屬性的變化可以通過監(jiān)聽UIDeviceOrientationDidChangeNotification來實時獲得設(shè)備方向的變化
UIInterfaceOrientation:
多數(shù)的旋轉(zhuǎn)都需要通過旋轉(zhuǎn)controller來實現(xiàn)。controller的方向也就是我上面提到的視圖方向,使用該枚舉UIInterfaceOrientation來表達。
如果想獲得當(dāng)前的視圖方向,可以通過以下代碼獲得
[UIApplication sharedApplication].statusBarOrientation
視圖方向的改變一定是由于設(shè)備方向發(fā)生了變化,設(shè)備方向的改變也只能是由于物理改變造成的。但是有一個例外,會在后面強制轉(zhuǎn)屏部分詳細說明。
如何讓Controller隨設(shè)備方向旋轉(zhuǎn)
1. 配置App支持的視圖方向
首先需要配置下當(dāng)前App能支持的視圖方向,所有使用的controller所能支持的視圖方向是該配置的子集;
可以通過以下兩種方法進行配置:
方法1,在Xcode選項中配置:
在Xcode->工程->General->Deployment Info中對Device Orientation進行配置,注意這里的Device Orientation不是上面所說的設(shè)備方向
方法2,在AppDelegate中通過代碼配置:
在AppDelegate中實現(xiàn)如下方法:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAll;
}
如果同時使用了兩種方式,那第二種使用代碼的方式會覆蓋第一種在Xcode中配置的方式
2. 配置Controller支持的方向
代碼很簡單,表示當(dāng)前Controller是否支持旋轉(zhuǎn),支持的方向都有什么,如下:
//是否支持轉(zhuǎn)屏
- (BOOL)shouldAutorotate
{
return YES;
}
//在支持轉(zhuǎn)屏的前提下,返回具體支持的方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
但是這段代碼具體要寫在哪個viewController中才有效果呢?
一個viewController是否可以旋轉(zhuǎn)并不是由他自己決定的,而是由rootViewController或者最上層被present上來的viewController來決定的。
每個App都有一個rootViewController,如果沒有通過present的方式推入controller,那么rootViewController支持的方向就決定了視圖方向;
如果有通過present的方式推入controller,那么最近的一次present操作對應(yīng)的controller就決定了視圖方向;
以上的代碼需要放在上面說到的決定了視圖方向的controller中
還有一個回調(diào)方法,決定了viewController出現(xiàn)時的視圖方向,該回調(diào)方法只對present方式進入界面的viewController有效果
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
當(dāng)然還有一點最重要的,記得要在下拉框中打開設(shè)備的轉(zhuǎn)屏鎖,要不然怎么轉(zhuǎn)ViewController都不會跟著轉(zhuǎn)的
關(guān)于強制轉(zhuǎn)屏
在上文中提過,設(shè)備方向改變一定是由于物理改變造成的,其實這并不絕對
蘋果的API曾經(jīng)是允許修改[UIDevice currentDevice].orientation的,后來這個接口被干掉作為私有API了
但是我們還是可以通過一些其他方式繞過蘋果的限制來設(shè)置[UIDevice currentDevice].orientation,設(shè)備方向一旦改變,再符合上述視圖方向改變的條件,視圖方向就會被旋轉(zhuǎn),從而達到強制轉(zhuǎn)屏的效果
在實際開發(fā)過程中也會遇到類似的場景,比如播放器的全屏操作,就是一次強制的視圖方向改變
代碼如下:
NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];
關(guān)于UINavigationController的旋轉(zhuǎn)
其實道理是一樣的,只不過如果present上來一個navigationController的時候,你需要想辦法讓這個navigationController去實現(xiàn)上述的shouldAutoRotate和supportedInterfaceOrientations方法,可以通過類別或繼承重寫的方式,這里也就不詳述了。
iOS的橫屏(Landscape)與豎屏(Portrait)InterfaceOrientation
0. 應(yīng)用級別的配置
大家(特指有iOS開發(fā)經(jīng)驗的人)應(yīng)該都知道Xcode Project的工程配置General頁簽中有那么四個圖(或者4個checkbox),標(biāo)識對四種interfaceOrientation的支持。分別為Portrait、PortraitUpsideDown、LandscapeLeft和LandscapeRight。
對應(yīng)的,在Xcode Project工程配置的Info頁,實際上就是Info.plist中,有對4種Orientation的記錄項。
這兩者是一樣的。
1. Window級別的控制
在iOS6.0之后,UIApplicationDelegate中多了一個方法聲明:
1- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
就是對于特定的application和特定的window,我們需要支持哪些interfaceOrientation,這是可以通過實現(xiàn)這個方法定制的。
返回值是一個無符號整數(shù),實際上是可以使用定義好的枚舉值:
typedefNS_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),
};
對于UIApplicationDelegate的這個方法聲明,大多數(shù)情況下application就是當(dāng)前的application,而window通常也只有一個。所以基本上通過window對橫屏豎屏interfaceOrientation的控制相當(dāng)于全局的。
2. Controller層面的控制
老版本的iOS有這樣一個方法:
1- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientationNS_DEPRECATED_IOS(2_0, 6_0);
即定制是否可以旋轉(zhuǎn)到特定的interfaceOrientation。
而在iOS6之后,推出了2個新的方法來完成這個任務(wù):
1
2
- (BOOL)shouldAutorotateNS_AVAILABLE_IOS(6_0);
- (NSUInteger)supportedInterfaceOrientationsNS_AVAILABLE_IOS(6_0);
可以看得出來,兩個和在一起就是原來任務(wù)的完成過程。其中,大概的判斷方式是,先執(zhí)行前者,判斷是否可以旋轉(zhuǎn),如果為YES,則根據(jù)是否支持特定的interfaceOrientation再做決斷。
3. 使得特定ViewController堅持特定的interfaceOrientation
iOS6之后還提供了這樣一個方法,可以讓你的Controller倔強第堅持某個特定的interfaceOrientation:
1- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentationNS_AVAILABLE_IOS(6_0);
這就叫堅持,呵呵!
當(dāng)然,使用這個方法是有前提的,就是當(dāng)前ViewController是通過全屏的Presentation方式展現(xiàn)出來的。
這里使用的是另外一套枚舉量,可以去UIApplication.h中查看定義。
4. 當(dāng)前屏幕方向interfaceOrientation的獲取
有3種方式可以獲取到“當(dāng)前interfaceOrientation”:
controller.interfaceOrientation,獲取特定controller的方向
[[UIApplication sharedApplication] statusBarOrientation] 獲取狀態(tài)條相關(guān)的方向
[[UIDevice currentDevice] orientation] 獲取當(dāng)前設(shè)備的方向
具體區(qū)別,可參見StackOverflow的問答:
http://stackoverflow.com/questions/7968451/different-ways-of-getting-current-interface-orientation
5. 容器Controller的支持
上面把interfaceOrientation方向的獲取和支持配置都說了,看起來沒什么問題了。有沒有什么特殊情況?
當(dāng)你使用TabbarController和NavigationController按照如上做法使用的時候就會有些頭疼。
辦法不是沒有,比較通俗的一種就是——繼承實現(xiàn)。
6. UIView的transform屬性強制旋轉(zhuǎn).
最后一個方法是設(shè)置UIView的transform屬性來強制旋轉(zhuǎn).
見下代碼:
//設(shè)置statusBar[[UIApplication sharedApplication] setStatusBarOrientation:orientation];//計算旋轉(zhuǎn)角度float arch;if (orientation == UIInterfaceOrientationLandscapeLeft)? ? arch = -M_PI_2;else if (orientation == UIInterfaceOrientationLandscapeRight)? ? arch = M_PI_2;else? ? arch = 0;//對navigationController.view 進行強制旋轉(zhuǎn)self.navigationController.view.transform = CGAffineTransformMakeRotation(arch);self.navigationController.view.bounds = UIInterfaceOrientationIsLandscape(orientation) ? CGRectMake(0, 0, SCREEN_HEIGHT, SCREEN_WIDTH) : initialBounds;