iOS 获取屏幕锁定状态


在开发中,有时候我们需要让某些特定的页面为横屏展示,而从这个页面离开或者进入其他页面时为竖屏,起初我仅仅依赖了UIViewController的屏幕旋转方法简单的处理,但是当需求慢慢在变化时,我发觉这并不能满足我的需求:

比如导航页需要横屏展示,但是从导航页上面某个按钮跳转的页面需要竖屏展示,而再从这个页面返回到导航页时,如果屏幕为竖屏且用户关闭了屏幕旋转锁定,则导航页应继续展示横屏。

下面笔记写的比较乱,可直接查看Demo in Github

屏幕旋转的方案

我现在使用的解决方案是CMMotionManager,启动设备的运动更新,通过给定的队列向给定的处理程序提供数据,对屏幕旋转的监测。

CMMotionManager可以理解为是CoreMotion Framework的中央管理器,也可以理解为运动服务。这些服务提供了获取加速机数据、旋转数据和磁力数据等。

首先我在info.plist中把设备的方向只勾选了竖屏。

AppDelegate 中需要实现的方法

- (UIInterfaceOrientationMask)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window {
    // iPhone doesn't support upside down by default, while the iPad does.  Override to allow all orientations always, and let the root view controller decide what's allowed (the supported orientations mask gets intersected).

    UIViewController * presentdeVC = [self.class topViewControllerWithPresentedViewController];
    if ([presentdeVC isKindOfClass:NSClassFromString(@"NaviController")])
        return UIInterfaceOrientationMaskAllButUpsideDown;

    return UIInterfaceOrientationMaskPortrait;
}

监听设备运动

/// 开启屏幕旋转的检测
- (void)startListeningDirectionOfDevice {
    if (self.motionManager == nil) {
        self.motionManager = [[CMMotionManager alloc] init];
    }

    // 提供设备运动数据到指定的时间间隔 刷新数据的评率
    self.motionManager.deviceMotionUpdateInterval = 0.3;

    // 判断设备传感器是否可用
    if (self.motionManager.deviceMotionAvailable) {
        // 启动设备的运动更新,通过给定的队列向给定的处理程序提供数据。
        [self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {

            [self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
        }];
    } else {
        [self setMotionManager:nil];
    }
}

停止监听设备运动

- (void)stopListeningDirectionOfDevice {
    if (_motionManager) {
        [_motionManager stopDeviceMotionUpdates];
        _motionManager = nil;
    }
}

根据设备运动,更新设备屏幕方向

- (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion {
    if ([UIDevice bb_canRotate] == false) {
        return;
    }

    double x = deviceMotion.gravity.x;
    double y = deviceMotion.gravity.y;

    if (fabs(y) >= fabs(x)) {// 竖屏
        if (y < 0) {
            if (self.previousInterfaceOrientation == UIInterfaceOrientationPortrait) {
                return;
            }
            [self applyInterfaceOrientation:UIInterfaceOrientationPortrait];
        }
        else {
            if (self.previousInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
                return;
            }
            [self applyInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown];
        }
    }
    else { // 横屏
        if (x < 0) {
            if (self.previousInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
                return;
            }
            [self applyInterfaceOrientation:UIInterfaceOrientationLandscapeRight];
        }
        else {
            if (self.previousInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
                return;
            }
            [self applyInterfaceOrientation:UIInterfaceOrientationLandscapeLeft];
        }
    }
}

强制修改屏幕方向

- (void)applyInterfaceOrientation:(UIInterfaceOrientation)orientation {
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

    UIDevice *currentDevice = [UIDevice currentDevice];
    UIDeviceOrientation currentDeviceOrientation = currentDevice.orientation;
    /// mark:强制旋转有时无效的解决方案: 强制前先设置为UIDeviceOrientationUnknown
    [currentDevice setValue:@(UIDeviceOrientationUnknown) forKey:@"orientation"];

    SEL selector = NSSelectorFromString(@"setOrientation:");
    if (![currentDevice respondsToSelector:selector]) {
        return;
    }
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
    [invocation setSelector:selector];
    [invocation setTarget:currentDevice];
    // 从2开始是因为0 1 两个参数已经被selector和target占用
    [invocation setArgument:&orientation atIndex:2];
    [invocation invoke];
    [currentDevice endGeneratingDeviceOrientationNotifications];
    [UIDevice setForceOrientation:currentDeviceOrientation];
}

获取屏幕锁定状态

现在存在的问题是,屏幕旋转开关被锁定时,横屏状态下不应该可以旋转页面,而Apple提供的api中,并没有获取控制中心中屏幕旋转锁定的开关

通过监听UIDeviceOrientationDidChangeNotification通知测试:

  • 当app在前台时,如果屏幕旋转开关锁定了,怎么旋转设备都不会触发此通知,只有在app启动、从后台进入前台或者被激活时,才会触发此通知
  • 当屏幕旋转开关被关闭时,设备旋转时都会触发此通知的

解决方法:

  • 注册UIDeviceOrientationDidChangeNotification通知

    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientaionDidChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
    }
  • 当触发监听的方法时,根据设备的方向确定屏幕是否可以旋转

/// 当用户锁定了屏幕旋转开关时,且app在前台时,设备旋转不会触发此通知,当app启动、从后台进入前台或者被激活时,都会触发此通知
+ (void)deviceOrientaionDidChange:(NSNotification *)noty {
    self.forceOrientation = [UIDevice currentDevice].orientation;
    [InterfaceOrientationUtil sharedInsatnce].previousInterfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;

    UIDevice *device = [UIDevice currentDevice] ;
    /**
     *  取得当前Device的方向,Device的方向类型为Integer
     *
     *  必须调用beginGeneratingDeviceOrientationNotifications方法后,此orientation属性才有效,否则一直是0。orientation用于判断设备的朝向,与应用UI方向无关
     *
     *  @param device.orientation
     *
     */
    NSLog(@"%@", noty.userInfo);

    switch (device.orientation) {
        case UIDeviceOrientationLandscapeLeft:
        case UIDeviceOrientationLandscapeRight:
            [self setBb_canRotate:YES];//只有当用户把手机旋转到横屏的时候来去触发判断是否支持横屏
            break;
        default:
            [self setBb_canRotate:NO];
            break;
    }
}

在监听设备的运动更新的方法中,根据canRotate判断是否可以旋转屏幕

附件: