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;
网友评论