iOS学习专题ios实用开发技巧技术网站加博客

iOS 屏幕旋转问题总结

2017-02-22  本文已影响5885人  MrJ的杂货铺

1、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

UIDeviceOrientation是硬件设备的方向,是随着硬件自身改变的,只能取值,不能设置。

2、UIInterfaceOrientation界面的显示方向

    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft

经测试发现如下结论。
当device处于UIInterfaceOrientationLandscapeLeft的状态,即相当于设备向左旋转,要想达到视觉上的统一,页面应该向右旋转,即[[UIApplication sharedApplication] setStatusBarOrientation: UIInterfaceOrientationLandscapeRight];其实设置完之后,再去获取UIInterfaceOrientation发现得到的是UIInterfaceOrientationLandscapeLeft,与官方文档并不矛盾。
这里其实是两个概念,一是旋转的方向,二是所处的方向,使用是要当心了!

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),
}

3、UIInterfaceOrientation的控制

3.1 被动控制

所谓的被动控制,即支持自动旋转Autorotate,UIInterfaceOrientation会随着UIDeviceOrientation的改变而自动改变,以达到视觉上的统一。是系统自动控制的,我们只能控制其能够支持自动旋转的方向,使其在有些方向上可以跟随旋转,有些方向上不能。主要有以下三种方式

Supported interface orientations.png
//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之间切换;

  1. 在rootViewController里加判断
  - (UIInterfaceOrientationMask)supportedInterfaceOrientations{
    if([[self topViewController] isKindOfClass:[subViewController class]])  
        return UIInterfaceOrientationMaskAllButUpsideDown;  
    else  
        return UIInterfaceOrientationMaskPortrait;
  }
  1. 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方法,但是会闪一下,依然不完美。

  1. 把需要单独设置的viewController添加在一个独立的navgationController
    这种方法不适用rootViewControllerUITabBarController的, 不推荐使用。
3.2 主动控制

所谓主动控制即不让其自动旋转Autorotate == NO,方向自行控制。主要有两种方式

//设置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;

注意:

  1. statusBar不会自己旋转,这里要首先设置statusBar的方向。iOS9后setStatusBarOrientation方法废除
  2. 我们这里选择的是self.navigationController进行旋转,当然也可以是self.view或者self.window,都可以,最好是全屏的view.
  3. 我们需要显示的设置bounds,UIKit并不知道你偷偷摸摸干了这些事情。
  4. -(BOOL)shouldAutorotate方法,应返回NO

在iOS 9 之后横屏时,状态栏会消失。

解决方法:确保Info.plist中的【View controller-based status bar appearance】YES,然后重写viewController- (BOOL)prefersStatusBarHidden,返回值是NO。详细参考iOS-UIStatusBar详细总结

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

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

上一篇 下一篇

猜你喜欢

热点阅读