横竖屏任意切换 实战浅析

前话:前前后后弄了一下午,尝试各种解决方案(估摸有一二十种吧),最后误打误撞终于解决,鉴于在国内国外查找各种解决方法都未能解决,故有此文。(当然stackOverFlow 各种新奇的解决方案,我一一尝试实战,却未能解决,可能是英文还不够好,理解国外解决方案的不够透彻所致,不过稍感欣慰,还是解决了;不管黑猫白猫,能抓耗子就是好猫吧;还真是应了这句话)

 

需求:整个APP中界面以竖屏为主,且不能自动横竖屏切换,个别播放视频页面可以根据手机的方向横竖屏切换或固定横屏;

 

苹果系统支持横屏顺序
默认读取plist里面设置的方向(优先级最高)等同于Xcode Geneal设置里面勾选
application window设置的级别次之
然后是UINavigationcontroller
级别最低的是viewcontroller

注:其实这个优先级跟你的window的rootViewcontroller有关系,如果rootViewcontroller是UITabbarViewontroller,那么tabbar就类似前面所说的UINavigationcontroller

 

翻阅各大网站和论坛我总结了好几种方式,但是大同小异的旋转屏幕的代码,以供参考:
支持ARC版本:

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 = UIInterfaceOrientationLandscapeRight;//这里可以改变旋转的方向
            [invocation setArgument:&val atIndex:2];
            [invocation invoke];
        }

或者

//强行旋转,(会导致程序崩溃,而且不会有崩溃日志,很难发现崩溃在哪里,所以使用这个方法的时候一定要注意设置支持的方向) -->  我反正没遇到 我用着好好的

如何手动旋转设备?

Objective-C:

NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];

 

不支持ARC版本

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
        [[UIDevice currentDevice] performSelector:@selector(setOrientation:)
                                       withObject:(id)UIInterfaceOrientationLandscapeRight];
    }

还有一点就是如何判断当前屏幕的方向:可以根据电源的现实方向来判断,苹果提供了这一方法

NSInteger i = [[UIApplication sharedApplication] statusBarOrientation];
if (i == UIInterfaceOrientationLandscapeRight){
  //在这里可以写相应的代码
}

有些人可能想要监听屏幕的自动旋转:

1.注册UIApplicationDidChangeStatusBarOrientationNotification通知(举例:在一个viewcontroller类的viewdidload中注册该通知),示例代码如下:
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationChange:)name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
- (void)statusBarOrientationChange:(NSNotification *)notification{
    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    if (orientation == UIInterfaceOrientationLandscapeRight) // home键靠右{
        //
    }
    if (orientation ==UIInterfaceOrientationLandscapeLeft) // home键靠左 {
        //
    }
    if (orientation == UIInterfaceOrientationPortrait){
        //
    }
    if (orientation == UIInterfaceOrientationPortraitUpsideDown){
        //
    }
}
注意这种方式监听的是StatusBar也就是状态栏的方向,所以这个是跟你的布局有关的,你的布局转了,才会接到这个通知,而不是设备旋转的通知。当我们关注的东西和布局相关而不是纯粹设备旋转,我们使用上面的代码作为实现方案比较适合。
2.注册UIDeviceOrientationDidChangeNotification通知(举例:我们同样在一个viewcontroller类的viewdidload中注册该通知),示例代码如下:

  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientChange:)name:UIDeviceOrientationDidChangeNotification object:nil];

- (void)orientChange:(NSNotification *)noti
{

    NSDictionary* ntfDict = [noti userInfo];

    UIDeviceOrientation  orient = [UIDevice currentDevice].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   */

           switch (orient)
        {
            case UIDeviceOrientationPortrait:

                break;
            case UIDeviceOrientationLandscapeLeft:


                break;
            case UIDeviceOrientationPortraitUpsideDown:


                break;
            case UIDeviceOrientationLandscapeRight:


                break;

            default:
                break;
        }
}
注意到这种方式里面的方向还包括朝上或者朝下,很容易看出这个完全是根据设备自身的物理方向得来的,当我们关注的只是物理朝向时,我们通常需要注册该通知来解决问题(另外还有一个加速计的api,可以实现类似的功能,该api较底层,在上面两个方法能够解决问题的情况下建议不要用,使用不当性能损耗非常大)。

 



 

1.在项目中用代码控制视图是否能够自动旋转,支持哪些方向主要是用了下面的三个方法:

 

[objc] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. // New Autorotation support.  
  2. //是否自动旋转,返回YES可以自动旋转  
  3. - (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;  
  4. //返回支持的方向  
  5. - (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;  
  6. // Returns interface orientation masks.  
  7. //这个是返回优先方向  
  8. - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;  

 

一般情况下实现前两个方法即可!!这些都是UIViewController的实例方法,直接在需要设置的控制器重写就行...

 

 

2.简单介绍我试过的集中方法

1.  这个方法别人说能用,我用了没效果,当时也没看别人的Demo  ; 可能有效果吧  ,Demo 见下方


[objc]
 view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. //强制旋转屏幕  
  2. - (void)orientationToPortrait:(UIInterfaceOrientation)orientation {  
  3.     SEL selector = NSSelectorFromString(@"setOrientation:");  
  4.     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];  
  5.     [invocation setSelector:selector];  
  6.     [invocation setTarget:[UIDevice currentDevice]];  
  7.     int val = orientation;  
  8.     [invocation setArgument:&val atIndex:2];//前两个参数已被target和selector占用  
  9.     [invocation invoke];  
  10.       
  11. }  


调用的时候只需把你想要的方向传过去即可!!

使用的时候有个点需要注意,从A进入B的时候,把B强制转换成横屏,返回的时候,需要在A出现的时候再转换为原来的方向,不然会有问题;个人建议可以在B的viewWillAppear调用这个方法,转换屏幕(例如转换为横屏),然后在A的viewWillAppear中转换回来;

 

最后附上以上内容的Demo:Demo地址

 

 

2.这里有一个用JS 和原生item 控制横竖屏切换的Demo。地址

 

 

3.我的方案:

其他界面不支持横屏:

播放界面横屏:

Step1:

在APPDelegate.h文件中增加属性:是否支持横屏

/***  是否允许横屏的标记 */
@property (nonatomic,assign)BOOL allowRotation;

在APPDelegate.m文件中增加方法,控制全部不支持横屏

-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if (self.allowRotation) {
        return UIInterfaceOrientationMaskAll;
    }
    return UIInterfaceOrientationMaskPortrait;
}

 Step2:

竖屏界面A 跳到 横屏界面B 

首先竖屏界面A 配置,直接贴代码了;

- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];

    NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];

    [[UIDevice currentDevice] setValue:value forKey:@"orientation"];

}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

{

    return UIInterfaceOrientationMaskPortrait;

}

 

横屏界面B

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

{

    return UIInterfaceOrientationMaskLandscape;

}

 

好,关键的两步来了;

1.竖屏界面A 跳到 横屏界面B  一定用presentViewController 跳转 (不知道我说的什么法的,下面的可以不看了)

注:.竖屏界面A---》CFMatchPlayBackViewController   横屏界面B:IJKVideoViewController

 IJKVideoViewController *ijk = [[IJKVideoViewController alloc] initWithURL:[NSURL URLWithString:self.replyPath]];

            CFMatchPlayBackViewController *VC =   ((CFMatchPlayBackViewController *)nextResponder);

        __weak __typeof(VC)weakSelf = VC;

            ijk.deviceTap = ^ {

                NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];

                [[UIDevice currentDevice] setValue:value forKey:@"orientation"];

            };

            [((UIViewController *)nextResponder) presentViewController:ijk  animated:YES completion:nil];

2.横屏界面B 跳转到 竖屏界面A 

    [self.presentingViewController dismissViewControllerAnimated:NO completion:^{

        //

        self.deviceTap();

    }];

 

3.重点:   本来这样跳,苹果本身自己是有一个Bug的,就是dismiss掉,回来依然是横屏,而且还是不自适应的横屏;不过解决办法就是 ---->  见下图

 

 4.UIView 横竖屏;最下层加一个View,利用UIView 旋转方法;不失为好方法,亲测也是可用的好办法;看需求吧

 

 

 

6.我的解决方案英文版 (思路一脉相承):

If the modal controller was in landscape orientation before dismissal, the presenting ViewController may not return to the origin orientation (portrait). The problem is because the AppDelegate supportedInterfaceOrientationsForWindow method is called before the controller is actually dismissed and the presented controller check still returns Landscape mask.

Set a flag to indicate whether the (modal) presented view controller will be displayed or not.

- (void)awakeFromNib // or where you instantiate your ViewController from
{
    [super awakeFromNib];
    self.presented = YES;
}

- (IBAction)exitAction:(id)sender // where you dismiss the modal
{
    self.presented = NO;
    [self dismissViewControllerAnimated:NO completion:nil];
}

And in the modal presented ViewController set the orientation according to the flag: When the modal ViewController is presented - return Landscape. When it is dismissed then return portrait

- (NSUInteger)supportedInterfaceOrientations
{
    if ([self isPresented]) {
        return UIInterfaceOrientationMaskLandscape;
    } else {
        return UIInterfaceOrientationMaskPortrait;
    }
}

Last step - from your AppDelegate call the modal presented ViewController for its orientation. I am just checking the currently presented ViewController and call the supportedInterfaceOrientations on it

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    NSUInteger orientationMask = UIInterfaceOrientationMaskPortrait;

    UIViewController *currentVC = self.window.rootViewController.presentedViewController; // gets the presented VC
    orientationMask = [currentVC supportedInterfaceOrientations];

    return orientationMask;
}

For more info check this link

 

后记: 写文章真的是费神费力;但鉴于找遍网上并没有很好的解决方案,故写此文,以帮助像我一样智商一般的孩子;最近赶项目很忙,等空了github 上传Demo; 留言要Demo的,传的会更快哟 !(这是有史以来我写的最清楚的文章,相信聪明的你已经Get到了)

posted on 2016-09-22 23:18  Jenaral  阅读(2893)  评论(0编辑  收藏  举报

导航