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。详细参考iOS-UIStatusBar详细总结
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"]; }
参考文章:
作者:MrJ的杂货铺
链接:https://www.jianshu.com/p/e473749f1c30