iOS屏幕旋转一直是一个清晰又模糊的概念,看似简单,但是对接过程中总会遇到这样那样的问题,比如屏幕闪烁、旋转不生效、状态栏消失等等一系列令人恶心的bug。笔者负责的项目,属于Reactnative&&ObjectC混合开发模式,目前的需要需要主app默认竖向展示,同时提供给Reactnative一个支持自动横竖屏的ViewController。特此针对iOS屏幕旋转进行了整理归纳,望对各位开发者有所帮助。
一、令人恶心的三种枚举解析
1、设备方向(物理旋转) - UIDeviceOrientation
硬件设备(ipad、iPhone)背身的旋转方向,以Home为参照物
//Portrait 表示纵向,Landscape 表示横向。
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
UIDeviceOrientationUnknown,
// Device oriented vertically, home button on the top
UIDeviceOrientationPortraitUpsideDown,
// Device oriented horizontally, home button on the right
UIDeviceOrientationLandscapeLeft,
// Device oriented horizontally, home button on the left
UIDeviceOrientationLandscapeRight,
// Device oriented flat, face up
UIDeviceOrientationFaceUp,
// Device oriented flat, face down
UIDeviceOrientationFaceDown
} __TVOS_PROHIBITED;
设备的旋转方向只读不可写
获取当前屏幕的方法:[UIDevice currentDevice].orientation
设备旋转监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceOrientationDidChange)
name:UIDeviceOrientationDidChangeNotification
object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
监听方法实现
- (BOOL)onDeviceOrientationDidChange{
//获取当前设备Device
UIDevice *device = [UIDevice currentDevice] ;
//识别当前设备的旋转方向
switch (device.orientation) {
case UIDeviceOrientationFaceUp:
NSLog(@"屏幕幕朝上平躺");
break;
case UIDeviceOrientationFaceDown:
NSLog(@"屏幕朝下平躺");
break;
case UIDeviceOrientationUnknown:
//系统当前无法识别设备朝向,可能是倾斜
NSLog(@"未知方向");
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(@"屏幕向左橫置");
break;
case UIDeviceOrientationLandscapeRight:
NSLog(@"屏幕向右橫置");
break;
case UIDeviceOrientationPortrait:
NSLog(@"屏幕直立");
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(@"屏幕直立,上下顛倒");
break;
default:
NSLog(@"无法识别");
break;
}
return YES;
}
2、页面旋转(视图旋转)- UIInterfaceOrientation
UIInterfaceOrientation程序界面的当前旋转方向(可以设置)
// Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa).
// This is because rotating the device to the left requires rotating the content to the right.
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
程序界面的方向使用UIInterfaceOrientation,与设备旋转方向没有任何关系
为了达到页面的流畅效果
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
3、页面旋转聚合(iOS6 后出现的)- UIInterfaceOrientationMask
支持多种页面旋转方向
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),
} __TVOS_PROHIBITED;
二、单页面旋转方向设置
实现方法
//方法1
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
//方法2
- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;
// Returns interface orientation masks.
//方法3
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;!
方法解析
方法1:页面是否支持自动旋转
方法2:页面支持的旋转方向有哪些
方法3:页面优先展示的旋转方向
三、两种屏幕旋转的触发方式
1、系统未关闭屏幕自动旋转功能
//1.决定当前界面是否开启自动转屏,如果返回NO,后面两个方法也不会被调用,只是会支持默认的方向
- (BOOL)shouldAutorotate {
return YES;
}
//2.返回支持的旋转方向
//iPad设备上,默认返回值UIInterfaceOrientationMaskAllButUpSideDwon
//iPad设备上,默认返回值是UIInterfaceOrientationMaskAll
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll;
}
//3.返回进入界面默认显示方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
2、系统关闭屏幕自动旋转功能
在程序界面通过点击等方式切换到横屏
// 方法1:
- (void)setInterfaceOrientation:(UIDeviceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:orientation]
forKey:@"orientation"];
}
}
//方法2:
- (void)setInterfaceOrientation:(UIInterfaceOrientation)orientation {
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice
instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
确保shouldAutorotate方法返回YES
两者使用的参数类型不
四、屏幕旋转控制的优先级
1、屏幕旋转设置方式
- Xcode的General设置
- Xcode的nfo.plist设置
- 代码设置Appdelegete中
2、旋转优先级
工程Target属性配置(全局权限) > Appdelegate&&Window > 根视图控制器> 普通视图控制器
3、开启App旋转的全局权限
Device Orientation属性配置
【General】—>【Deployment Info】—>【Device Orientation】
值得注意的是,对于iPhone,如果四个属性我们都选或者都不选,效果和默认的情况一样
Info.Plist设置
Supported interface orientation
与第一种方式一样的效果,两种方式最终都是设置info.plist中的属性
Appdelegate&&Window中设置
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}
如果我们实现了Appdelegate的这一方法,那么我们的App的全局旋转设置将以这里的为准,即使前两种方法的设置与这里的不同。
五、设置单页面的旋转权限
开发中常涉及到的控制器
UITabbarViewController,UINavigationBarController ,UIViewController
我们的项目都是用UITabbarViewController作为Window的根视图控制器,然后管理着若干个导航控制器UINavigationBarController,再由导航栏控制器去管理普通的视图控制器UIViewController。若以此为例的话,关于旋转的优先级从高到低就是UITabbarViewController>UINavigationBarController >UIViewController了。如果具有高优先级的控制器关闭了旋转设置,那么低优先级的控制器是无法做到旋转的。
比如说我们设置要单个视图控制器可以自动旋转,这需要在视图控制器中增加shouldAutorotate方法返回YES或者NO来控制。但如果存在上层根视图控制器,而我们只在这个视图控制器中实现方法,会发现这个方法是不走的,因为这个方法被上层根视图控制器拦截了
六、实现自动可控的旋转
1、逐级实现
1、UITabbarViewController
//是否自动旋转
-(BOOL)shouldAutorotate{
return self.selectedViewController.shouldAutorotate;
}
//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.selectedViewController supportedInterfaceOrientations];
}
//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
UINavigationController
//是否自动旋转
//返回导航控制器的顶层视图控制器的自动旋转属性,因为导航控制器是以栈的原因叠加VC的
//topViewController是其最顶层的视图控制器,
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations];
}
//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
其实就是高优先级的视图控制器要跟随低优先级控制器的旋转配置。这样就能够达到目的
2、使用模态视图
使用模态视图可以不受这种根视图控制器优先级的限制。这个也很容易理解,模态弹出的视图控制器是隔离出来的,不受根视图控制的影响。具体的设置和普通视图器代码相同
七、实现需求
App主要主界面竖向展示,部分页面横向展示
两种解决方案
1、逐级控制
步骤方式
1.开启全局权限设置项目支持的旋转方向
2.自定义标签控制器和导航控制器来设置屏幕的自动旋转。
3.自定义基类控制器设置不支持自动转屏,并默认只支持竖屏
4.对项目中需要转屏幕的控制器开启自动转屏、设置支持的旋转方向并设置默认方向
2、通过全局监听当前的方向变化
步骤方式
1.在Applegate文件中增加一个用于记录当前屏幕是否横屏的属性
2.需要横屏的界面,进入界面后强制横屏,离开界面时恢复竖屏
核心代码
1、添加监听
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onDeviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification
object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
2、实现监听
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
if (_allowAutoRotate) {
//只支持横屏
return UIInterfaceOrientationMaskLandscape;
}else{
//支持竖屏
return UIInterfaceOrientationMaskPortrait;
}
}
3、页面控制
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
delegate.allowAutoRotate = YES;
//进入界面:设置横屏
[self setDeviceInterfaceOrientation:UIDeviceOrientationLandscapeLeft];
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
delegate.allowAutoRotate = NO;
//离开界面:设置竖屏
[self setDeviceInterfaceOrientation:UIDeviceOrientationPortrait];
}
八、优化显示
视图切换显示异常问题
当前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];
}];
}
但是会出现屏幕闪烁问题,不推荐使用此方案,可以通过强制设置屏幕方向,在视图的钩子函数中进行手动控制
屏幕旋转后,状态栏无法显示
设置info.plist 中 View controller-based status bar appearance YES
对应的控制其中实现下面代码
//设置样式
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
//设置是否隐藏
- (BOOL)prefersStatusBarHidden {
// [super prefersStatusBarHidden];
return NO;
}
//设置隐藏动画
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return UIStatusBarAnimationNone;
}
作者:WirelessSprucetec
链接:https://juejin.im/post/5d4a790af265da039401f305
来源:掘金
网友评论