首页 > 系统 > iOS > 正文

iOS 屏幕旋转问题总结

2019-11-08 00:18:33
字体:
来源:转载
供稿:网友

1、两个Orientation

1.1设备的物理方向(UIDeviceOrientation)

 typedef NS_ENUM(NSInteger, UIDeviceOrientation) { 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}

1.2界面的显示方向(UIInterfaceOrientation)

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft}

注意:

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft

两者是相反的,经过测试发现: 在【General】 -->【Deployment Info】-->【Device Orientation】勾选Landscape Right 设备支持的方向是听筒在左,Home键在右的方向,而 此时打印的device方向是UIDeviceOrientationLandscapeLeft 打印的Interface的方向是UIInterfaceOrientationLandscapeLeft

这个是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),}

1.3获取Orientation

设备 [UIDevice currentDevice].orientation @PRoperty(nonatomic,readonly) UIDeviceOrientation orientation; // return current device orientation. this will return UIDeviceOrientationUnknown unless device orientation notifications are being generated.

如果关闭了系统的横竖屏切换开关,即系统层级只允许竖屏时,再通过上述方式获取到的设备方向将是UIDeviceOrientationPortrait。 - 界面 self.interfaceOrientation ios之后8废除 [UIapplication sharedApplication].statusBarOrientation

2、默认方向

2.1 Device

打开【General】 -->【Deployment Info】-->【Device Orientation】 可以发现Xcode默认支持的设备方向有PortraitLandscape LeftLandscape Right,Upside Down 方向是不支持的。 默认支持方向.png

此处我们把Upside Down勾中,发现在ipad中已经默认支持四个方向,但在iPhone中UpsideDown方向不会发生变化。

原因: **此处是设置支持的设备方向,界面看到的方向是Interface的方向,此时在全部选中的情况下(UIInterfaceOrientationMask)supportedInterfaceOrientations方法, iPad默认返回UIInterfaceorientationMaskAll, iPhone默认返回UIInterfaceOrientationMaskAllButUpsideDown。 iPhone若要支持UpsideDown,需重写(UIInterfaceOrientationMask)supportedInterfaceOrientations**

具体设置方法,下面再详细介绍。

2.2 Interface

打开【Info】 -->【Custom iOS Target Properties】-->【Supported interface orientations】 默认和【General】 -->【Deployment Info】-->【Device Orientation】值一样

Interface默认支持方向.png

3、全局设置

Device的方向是我们是通过改变握持方向去改变的,不是代码能够改变的,所以这里对Device Orientation只有Support的概念。通过【General】 -->【Deployment Info】-->【Device Orientation】设置。

设置Interface Orientation有三种途径

3.1 Info.plist设置

设置Interface方向.png 如图所示,可以通过修改Info.plist里的UISupportedInterfaceOrientations的属性修改InterfaceSupportOrientation

初始化界面方向.png

设置Interface的界面的Orientationplist里并没有直接给出,需要我们自行添加,添加方式和UISupportedInterfaceOrientations相同。

注意: 【General】 -->【Deployment Info】-->【Device Orientation】设置设备支持方向时,plist里的Supported interface orientations会根据设备方向自动添加Interface的支持方向。如不需要某方向的UISupportedInterfaceOrientations,可以在Info.plist里手动修改,该修改并不会影响DeviceSupportOrientation

3.2 UIWindow设置

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

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

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

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

这三个方法是iOS6之后才有的,有个特点是只在两种情况下才生效

1.当前viewControllerwindowrootViewController 2.当前viewControllermodal模式的,即此viewController是调用presentModalViewController而显示出来的.

在以上两种情况中,supportedInterfaceOrientations的方法会作用于当前的viewController和所有的childViewController,如果不是以上的两种情况下的viewControllerUIKit不会执行上述方法。

**这三种方法控制规则的交集就是一个viewController的最终支持的方向; 如果最终的交集为空,在iOS6以后会抛出UIApplicationInvalidInterfaceOrientationException崩溃异常。**

4、自定义设置

在实际的项目需求过程中,往往是有一些需要固定方向,有些需要支持旋转适应的。刚才上面提到过,通过UIViewController的三个方法设置Orientation时,只有在是windowrootViewController或者modal模式下才生效。单独设置某个viewController并没有效果。这样就给我们带来了一些麻烦,通过调查研究,大概有这么几种解决方案。

4.1 在rootViewController里加判断

如:

- (UIInterfaceOrientationMask)supportedInterfaceOrientations{ if([[self topViewController] isKindOfClass:[subViewController class]]) return UIInterfaceOrientationMaskAllButUpsideDown; else return UIInterfaceOrientationMaskPortrait;}

或者在UINavigationControllerUITabBarController里重写

-(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方法,但是会闪一下,依然不完美。

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

这种方法不适用rootViewControllerUITabBarController的, 不推荐使用。

4.3 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方法废除,在ios10中测试,不设置StatusBarOrientation会自动调整 我们这里选择的是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详细总结

- (BOOL)prefersStatusBarHidden{ return NO;}

完美的方法待继续研究!!!

5、强制旋转

5.1 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"];}

5.2 UIview.transform的方法

方法见4.3

参考文章: iOS屏幕旋转学习笔记 [iOS]Orientation 想怎么转就怎么转 iOS 知识小集(横竖屏切换) iOS强制改变物理设备方向的进阶方法


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表