天天看点

iOS屏幕旋转学习笔记

一、两种orientation

了解屏幕旋转首先需要区分两种orientation

1、device orientation

设备的物理方向

2、interface orientation

界面显示的方向

ios提供了在设备旋转时,界面显示发生相应适配的能力,以达到方便用户使用并提供最佳显示效果的目的。开发者需要指定应用支持的显示方向,并对界面显示做出对应的适配。由于界面适配的工作量相当大,目前国内的应用大都只支持默认的竖屏方向。

二、相关枚举定义

1、ios 5和之前版本(后文均简称ios5):

typedef ns_enum(nsinteger, uiinterfaceorientation) { 

    uiinterfaceorientationportrait           = uideviceorientationportrait, 

    uiinterfaceorientationportraitupsidedown = uideviceorientationportraitupsidedown, 

    uiinterfaceorientationlandscapeleft      = uideviceorientationlandscaperight, 

    uiinterfaceorientationlandscaperight     = uideviceorientationlandscapeleft 

}; 

2、ios 6和之后版本(后文均简称ios6)又新增了:

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), 

ios 6使用 ns_options 的方式重新定义了uiinterfaceorientationmaskportrait、uiinterfaceorientationmasklandscapeleft、uiinterfaceorientationmasklandscaperight、uiinterfaceorientationmaskportraitupsidedown几种基础枚举,这就意味着能以组合的方式更加方便的使用这些枚举值。

三、相关方法

1、ios 5中控制屏幕旋转的方法:

// applications should use supportedinterfaceorientations and/or shouldautorotate.. 

- (bool)shouldautorotatetointerfaceorientation:(uiinterfaceorientation)tointerfaceorientation ns_deprecated_ios(2_0, 6_0); 

如果打算支持 tointerfaceorientation 对应的方向就返回 yes,否则返回 no。

2、ios6中控制屏幕旋转相关方法:

// new autorotation support. 

- (bool)shouldautorotate ns_available_ios(6_0); 

- (nsuinteger)supportedinterfaceorientations ns_available_ios(6_0); 

// returns interface orientation masks. 

- (uiinterfaceorientation)preferredinterfaceorientationforpresentation ns_available_ios(6_0); 

第一个方法决定是否支持多方向旋转屏,如果返回no则后面的两个方法都不会再被调用,而且只会支持默认的uiinterfaceorientationmaskportrait方向;

第二个方法直接返回支持的旋转方向,该方法在ipad上的默认返回值是uiinterfaceorientationmaskall,iphone上的默认返回值是uiinterfaceorientationmaskallbutupsidedown,详情见官方q&a文档;

第三个方法返回最优先显示的屏幕方向,比如同时支持portrait和landscape方向,但想优先显示landscape方向,那软件启动的时候就会先显示landscape,在手机切换旋转方向的时候仍然可以在portrait和landscape之间切换;

3、attemptrotationtodeviceorientation方法

从ios 5开始有了这个新方法:

// call this method when your return value from shouldautorotatetointerfaceorientation: changes 

// if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return yes from shouldautorotatetointerfaceorientation: 

+ (void)attemptrotationtodeviceorientation ns_available_ios(5_0); 

该方法的使用场景是 interface orientation和device orientation 不一致,但希望通过重新指定 interface orientation 的值,立即实现二者一致;如果这时只是更改了支持的 interface orientation 的值,没有调用attemptrotationtodeviceorientation,那么下次 device orientation 变化的时候才会实现二者一致,关键点在于能不能立即实现。

举个例子:

假设当前的 interface orientation 只支持 portrait,如果 device orientation 变成 landscape,那么 interface orientation 仍然显示 portrait;

如果这时我们希望 interface orientation 也变成和 device orientation 一致的 landscape,以ios 6 为例,需要先将 supportedinterfaceorientations 的返回值改成landscape,然后调用 attemptrotationtodeviceorientation方法,系统会重新询问支持的 interface orientation,已达到立即更改当前 interface orientation 的目的。

四、如何决定interface orientation

1、全局控制

info.plist文件中,有一个supported interface orientations,可以配置整个应用的屏幕方向,此处为全局控制。

2、uiwindow

ios6的uiapplicationdelegate提供了下述方法,能够指定 uiwindow 中的界面的屏幕方向:

- (nsuinteger)application:(uiapplication *)application supportedinterfaceorientationsforwindow:(uiwindow *)window  ns_available_ios(6_0); 

该方法默认值为 info.plist 中配置的 supported interface orientations 项的值。

ios中通常只有一个 window,所以此处的控制也可以视为全局控制。

3、controller

只有以下两种情况:

当前controller是window的rootviewcontroller

当前controller是modal模式的时,orientations相关方法才会起作用(才会被调用),当前controller及其所有的childviewcontroller都在此作用范围内。

4、最终支持的屏幕方向

前面所述的3种控制规则的交集就是一个controller的最终支持的方向;

如果最终的交集为空,在ios6以后会抛出uiapplicationinvalidinterfaceorientationexception崩溃异常。

四、强制屏幕旋转

如果interface和device方向不一样,想强制将interface旋转成device的方向,可以通过attemptrotationtodeviceorientation实现,但是如果想将interface强制旋转成任一指定方向,该方式就无能为力了。

不过聪明的开发者们总能想到解决方式:

1、私有方法

[[uidevice currentdevice] setorientation:uiinterfaceorientationportrait]; 

但是现在苹果已经将该方法私有化了,越狱开发的同学可以试试。

2、旋转view的transform

也可以通过旋转view的transform属性达到强制旋转屏幕方向的目的,但个人感觉这不是靠谱的思路,可能会带来某些诡异的问题。

3、主动触发 orientation 机制

要是能主动触发系统的 orientation 机制,调用 orientation 相关方法,使新设置的 orientation 值起作用就好了。这样只要提前设置好想要支持的 orientation,然后主动触发 orientation 机制,便能实现将 interface orientation旋转至任意方向的目的。

万能的stackoverflow上提供了一种主动触发的方式:

在ios 4和ios 6以后:

uiviewcontroller *vc = [[uiviewcontroller alloc]init]; 

[self presentmodalviewcontroller:vc animated:no]; 

[self dismissmodalviewcontrolleranimated:no]; 

[vc release]; 

ios 5中:

uiwindow *window = [[uiapplication sharedapplication] keywindow]; 

uiview *view = [window.subviews objectatindex:0]; 

[view removefromsuperview]; 

[window addsubview:view]; 

这种方式会触发uikit重新调用controller的orientation相关方法,以达到在device方向不变的情况下改变interface方向的目的。

虽然不优雅,但却能解决问题,凑合吧。。

ps:

话说ios8中的屏幕旋转相关方法又变化了,表示适配起来很蛋疼。

继续阅读