iOS 屏幕旋转问题总结

1、UIDeviceOrientation 设备的物理方向

简介

UIDeviceOrientation即我们手持的移动设备的Orientation,是一个三围空间,故有六个方向:

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

获取

通过[UIDevice currentDevice].orientation获取当前设备的方向

当关闭了系统的横竖屏切换开关,即系统层级只允许竖屏时,再通过上述方式获取到的设备方向将只是UIDeviceOrientationPortrait。

UIDeviceOrientation是硬件设备的方向,是随着硬件自身改变的,只能取值,不能设置。

2、UIInterfaceOrientation界面的显示方向

简介

UIInterfaceOrientation即我们看到的视图的Orientation,可以理解为statusBar所在的方向,是一个二维空间,有四个方向:

UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,

UIInterfaceOrientationPortrait          = UIDeviceOrientationPortrait,

UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,

UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,

UIInterfaceOrientationLandscapeRight    = UIDeviceOrientationLandscapeLeft

获取

1.viewController. interfaceOrientation该方法在 iOS8之后废除。

2.[UIApplication sharedApplication].statusBarOrientation即状态栏的方向。

关联

UIDeviceOrientation与UIInterfaceOrientation是两个互不相干的属性。

其中一个并不会随另外一个变化而变化。但大多数情况下,两者会一起出现,主要是为了达到视觉的统一。

注意:

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,

UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

两者是相反的,但是根据官方文档,如下所示,我们发现两者的方向是一致的。

DeviceOrientation.png

InterfaceOrientation.png

经测试发现如下结论。

当device处于UIInterfaceOrientationLandscapeLeft的状态,即相当于设备向左旋转,要想达到视觉上的统一,页面应该向右旋转,即[[UIApplication sharedApplication] setStatusBarOrientation: UIInterfaceOrientationLandscapeRight];其实设置完之后,再去获取UIInterfaceOrientation发现得到的是UIInterfaceOrientationLandscapeLeft,与官方文档并不矛盾。

这里其实是两个概念,一是旋转的方向,二是所处的方向,使用是要当心了!

扩展

UIInterfaceOrientationMask

这是ios6之后新增的一组枚举值,使用组合时更加方便,使用时根据返回值类型选择正确的格式,避免可能出现的bug

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

}

3、UIInterfaceOrientation的控制

3.1 被动控制

所谓的被动控制,即支持自动旋转Autorotate,UIInterfaceOrientation会随着UIDeviceOrientation的改变而自动改变,以达到视觉上的统一。是系统自动控制的,我们只能控制其能够支持自动旋转的方向,使其在有些方向上可以跟随旋转,有些方向上不能。主要有以下三种方式

3.1.1 【Targets】中设置

【General】-->【Deployment Info】-->【Device Orientation】

Device Orientation.png

这里虽然命名为DeviceOrientation,但实际上这里表示其界面支持的自动旋转的方向。

为什么这么说呢,因为这个地方的设置的值和info.plist文件里Supported interface orientations值是同步的,修改其中一个,另一个也会随之改变。另外,不论我们这里怎么勾选,对程序当中获取当前设备的orientation是没有影响的。

Supported interface orientations.png

3.1.2 UIWindow设置

iOS6的UIApplicationDelegate提供了下述方法,能够指定UIWindow中的界面的屏幕方向:

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window NS_AVAILABLE_IOS(6_0);

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

3.1.3 UIViewController设置

通过三个代理方法设置

//Interface的方向是否会跟随设备方向自动旋转,如果返回NO,后两个方法不会再调用

- (BOOL)shouldAutorotate {

return YES;

}

//返回直接支持的方向

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{

return UIInterfaceOrientationMaskPortrait;

}

//返回最优先显示的屏幕方向

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {

return UIInterfaceOrientationPortrait;

}

解释:

1.第二个方法,在iPad上的默认返回值是UIInterfaceOrientationMaskAll,iPhone上的默认返回值是UIInterfaceOrientationMaskAllButUpsideDown;

2.在前面DeviceOrientation即使全部勾选了,若要iPhone支持UpsideDown,也要在viewcontroller里重写第二个方法。返回包含UpsideDown的方向;

3.第三个方法,比如同时支持Portrait和Landscape方向,但想优先显示Landscape方向,那软件启动的时候就会先显示Landscape,在手机切换旋转方向的时候仍然可以在Portrait和Landscape之间切换;

3.1.4 总结

1.这三种方式控制规则的交集就是一个viewController的最终支持的方向;

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

2.如果关闭了系统的横竖屏切换开关,即系统层级只允许竖屏时,再通过上述方式获取到的设备方向将是UIDeviceOrientationPortrait。UIInterfaceOrientation也将不会改变。

3.第三种方式只有在当前viewController是window的rootViewController。或者是通过presentModalViewController而显示出来的.才会生效。作用于viewController及其childViewController。否则UIKit并不会执行上述方法。

3.1.5 灵活控制

上述方法基本上可以认为是一种全局设置,实际项目中可能会是一个或几个页面需要单独控制。通过UIViewController的三个方法设置Orientation时,只有在是window的rootViewController或者modal模式下才生效。且作用于其childViewController单独设置某个viewController并没有效果。这种情况主要可以通过下面几种方法解决。

1.在rootViewController里加判断

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{

if([[self topViewController] isKindOfClass:[subViewController class]]) 

return UIInterfaceOrientationMaskAllButUpsideDown; 

else

return UIInterfaceOrientationMaskPortrait;

}

2.在UINavigationController或UITabBarController里重写

-(UIInterfaceOrientationMask)supportedInterfaceOrientations

{

return self.selectedViewController.supportedInterfaceOrientations;

}

-(BOOL)shouldAutorotate

{

return [self.selectedViewController shouldAutorotate];

}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation

{

return [self.selectedViewController preferredInterfaceOrientationForPresentation];

}

UINavigationController 使用self.topViewController

UITabBarController 使用self.selectedViewController

然后在viewController重写这三个方法,这样就巧妙的绕开了UIKit只调用rootViewController的方法的规则. 把决定权交给了当前正在显示的viewController.

但是

这样是可以在当前viewController达到预期效果,但是在返回上一页时,或者在当前页面不不支持的方向的上一页进来时,不能立即达到预期状态,需要设备方向更换一次才能恢复正常。

解决方案:

#pragma mark -UITabBarControllerDelegate

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {

[self presentViewController:[UIViewController new] animated:NO completion:^{

[self dismissViewControllerAnimated:NO completion:nil];

}];

}

#pragma mark -UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

[self presentViewController:[UIViewController new] animated:NO completion:^{

[self dismissViewControllerAnimated:NO completion:nil];

}];

}

这样就会触发-(BOOL)shouldAutorotate方法和

-(UIInterfaceOrientationMask)supportedInterfaceOrientations方法,但是会闪一下,依然不完美。

3.把需要单独设置的viewController添加在一个独立的navgationController里

这种方法不适用rootViewController是UITabBarController的, 不推荐使用。

3.2 主动控制

所谓主动控制即不让其自动旋转Autorotate == NO,方向自行控制。主要有两种方式

3.2.1 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;

注意:

statusBar不会自己旋转,这里要首先设置statusBar的方向。iOS9后setStatusBarOrientation方法废除

我们这里选择的是self.navigationController进行旋转,当然也可以是self.view或者self.window,都可以,最好是全屏的view.

我们需要显示的设置bounds,UIKit并不知道你偷偷摸摸干了这些事情。

-(BOOL)shouldAutorotate方法,应返回NO

在iOS 9 之后横屏时,状态栏会消失。

解决方法:确保Info.plist中的【View controller-based status bar appearance】为YES,然后重写viewController的- (BOOL)prefersStatusBarHidden,返回值是NO。

3.2.2 强制旋转setOrientation

setOrientation 在iOS3以后变为私有方法了,不能直接去调用此方法,否则后果就是被打回。

不能直接调用,但是可以间接的去调用,下面的方法就是利用 KVO机制去间接调用,多次验证不会被打回,放心!

-(void)viewWillAppear:(BOOL)animated{

//首先设置UIInterfaceOrientationUnknown欺骗系统,避免可能出现直接设置无效的情况

NSNumber *orientationUnknown = [NSNumber numberWithInt:UIInterfaceOrientationUnknown];

[[UIDevice currentDevice] setValue:orientationUnknown forKey:@"orientation"];

NSNumber *orientationTarget = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];

[[UIDevice currentDevice] setValue:orientationTarget forKey:@"orientation"];

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容