1.两种屏幕旋转的触发方式
我们开发的App的,大多情况都是大多界面支持竖屏,几个特别的界面支持旋转横屏,两种界面相互切换,触发其旋转有两种情况:
情况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,这样这两个方法才会生效。还要注意两者使用的参数类型不同。
看到这里不太明白NSInvocation是在做神马,去查了一下资料:
一、简介
- 在 iOS中可以直接调用某个对象的消息方式有两种:
- 一种是 performSelector:withObject;
- 再一种就是 NSInvocation。
- 第一种方式比较简单,能完成简单的调用。但是对于 >2个的参数或者有返回值的处理,那就需要做些额外工作才能搞定。那么在这种情况下,我们就可以使用NSInvocation来进行这些相对复杂的操作。
二、使用方式
- 代码:
- (void)viewDidLoad {
[super viewDidLoad];
//NSInvocation;用来包装方法和对应的对象,它可以存储方法的名称,对应的对象,对应的参数,
/*
NSMethodSignature:签名:再创建NSMethodSignature的时候,必须传递一个签名对象,签名对象的作用:用于获取参数的个数和方法的返回值
*/
//创建签名对象的时候不是使用NSMethodSignature这个类创建,而是方法属于谁就用谁来创建
NSMethodSignature*signature = [ViewController instanceMethodSignatureForSelector:@selector(sendMessageWithNumber:WithContent:)];
//1、创建NSInvocation对象
NSInvocation*invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
//invocation中的方法必须和签名中的方法一致。
invocation.selector = @selector(sendMessageWithNumber:WithContent:);
/*第一个参数:需要给指定方法传递的值
第一个参数需要接收一个指针,也就是传递值的时候需要传递地址*/
//第二个参数:需要给指定方法的第几个参数传值
NSString*number = @"1111";
//注意:设置参数的索引时不能从0开始,因为0已经被self占用,1已经被_cmd占用
[invocation setArgument:&number atIndex:2];
NSString*number2 = @"啊啊啊";
[invocation setArgument:&number2 atIndex:3];
//2、调用NSInvocation对象的invoke方法
//只要调用invocation的invoke方法,就代表需要执行NSInvocation对象中制定对象的指定方法,并且传递指定的参数
[invocation invoke];
}
- (void)sendMessageWithNumber:(NSString*)number WithContent:(NSString*)content{
NSLog(@"电话号%@,内容%@",number,content);
}
下边结合自己做项目的过程说一下思路,项目中某几个页面需要实现转屏效果,项目结构是rootViewController为一个UITabbarController,子视图都是UINavigationController加载的viewcontroller。
首先在项目target->General里设置Device Orientation为项目支持的所有转屏类型,我项目勾选了Portrait、left、right三种。
项目中,我只有几个页面需要支持转屏,那么怎么确保其他页面不受影响呢?这里就要了解一下转屏的权限优先级:
控制屏幕旋转优先级为: Appdelegate&&Window > 根视图控制器> 普通视图控制器
在一般情况下,我们的项目都是用UITabbarViewController作为Window的根视图控制器,然后管理着若干个导航控制器UINavigationBarController,再由导航栏控制器去管理普通的视图控制器UIViewController。若以此为例的话,关于旋转的优先级从高到低就是UITabbarViewController>UINavigationBarController >UIViewController了。如果具有高优先级的控制器关闭了旋转设置,那么低优先级的控制器是无法做到旋转的。
比如说我们设置要单个视图控制器可以自动旋转,这需要在视图控制器中增加shouldAutorotate方法返回YES或者NO来控制。但如果存在上层根视图控制器,而我们只在这个视图控制器中实现方法,会发现这个方法是不走的,因为这个方法被上层根视图控制器拦截了。理解这个原理后,我们可以这样实现自动逐级设置各视图控制器,高优先级的视图控制器影响低优先级控制器,
解决上述的问题我们需要设置BaseTabbarViewController(根视图控制器继承该类)如下:它的返回值依赖于当前选中的UINavigationController的值
//是否自动旋转
-(BOOL)shouldAutorotate{
return self.selectedViewController.shouldAutorotate;
}
//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.selectedViewController supportedInterfaceOrientations];
}
//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}
设置导航控制器BaseNavigationController(所有项目中的navigation继承该类)如下:它的值依赖于当前展示的页面返回值
//是否自动旋转
//返回导航控制器的顶层视图控制器的自动旋转属性,因为导航控制器是以栈的原因叠加VC的
//topViewController是其最顶层的视图控制器,
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
//支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations];
}
//默认方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
到这里,我们就应该明白了,其实就是高优先级的视图控制器要跟随低优先级控制器的旋转配置。这样就能够达到目的。
项目中我们要保证其他页面不支持转屏,因此在BaseViewController(项目中所有的controller继承与此)中添加代码:
- (BOOL)shouldAutorotate{
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
这样就所有页面都不支持转屏了,只在你想要实现转屏的controller中重写这三个方法返回你想要的值就可以了。
遇到的坑
但是问题来了,这样设置过后遇到一个奇怪的问题,就是把手机横屏启动程序,界面统统变形了,视图是以横屏时候的宽高来布局的,虽然已经禁用了转屏(也确实不会转屏)但初始化构建的界面却变形了。于是这里使用了上边说过的invoke方法强制恢复成了竖屏的样式,在BaseTabbarViewController和BaseViewController的viewdidload中添加代码
- (void)restoreStatusBarOrientation {
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 = UIDeviceOrientationPortrait;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
这样所有界面无论横屏竖屏的状态下打开都为竖屏的样式了,最后就可以把精力放到支持转屏的界面进行开发了。
如何适配转屏的界面呢,通过通知监听横竖屏变化再改变你界面的布局就好了
//屏幕旋转通知
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDeviceOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil
];
- (void)handleDeviceOrientationDidChange:(UIInterfaceOrientation)interfaceOrientation{
UIDevice *device = [UIDevice currentDevice] ;
switch (device.orientation) {
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationUnknown:
case UIDeviceOrientationPortraitUpsideDown:
break;
case UIDeviceOrientationLandscapeLeft:
case UIDeviceOrientationLandscapeRight:
{
//横屏
}
break;
case UIDeviceOrientationPortrait:
{
//竖屏
}
break;
default:
break;
}
}
我是自定义了一些宏 来保证各个机型和横屏竖屏都尽可能自动去返回理想值,这里就智者见智仁者见仁了。
这篇博客:https://blog.csdn.net/DreamcoffeeZS/article/details/79037207这里讲得比较全面 可以参考
还有一种思路是整个项目并不支持横竖屏转动,是通过动画旋转指定界面来实现的转屏效果,这个如果想了解可以去搜索其他文章参考。