IOS横竖屏

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这里讲得比较全面  可以参考

还有一种思路是整个项目并不支持横竖屏转动,是通过动画旋转指定界面来实现的转屏效果,这个如果想了解可以去搜索其他文章参考。

猜你喜欢

转载自blog.csdn.net/cc835297454/article/details/83998564