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