iOS设备方向无视方向锁定

Posted

技术标签:

【中文标题】iOS设备方向无视方向锁定【英文标题】:iOS device orientation disregarding orientation lock 【发布时间】:2011-01-01 16:28:44 【问题描述】:

我想查询 iPhone 当前所处的方向。使用

[UIDevice currentDevice].orientation

只要设备没有方向锁定就可以工作。但是,如果它被锁定,它总是以锁定的方向响应,而不是设备的实际方向。

有没有获得实际设备方向的高级方法?

【问题讨论】:

【参考方案1】:

你也可以使用 CoreMotion

方向检测算法:

如果 abs( y )

如果您的 iPhone 处于纵向位置,请查看 y 符号以检测向上或倒置。

如果您对正面或向下感兴趣,请查看 z 值。


import CoreMotion

var uMM: CMMotionManager!

override func
viewWillAppear( p: Bool ) 
    super.viewWillAppear( p )
    uMM = CMMotionManager()
    uMM.accelerometerUpdateInterval = 0.2

    //  Using main queue is not recommended. So create new operation queue and pass it to startAccelerometerUpdatesToQueue.
    //  Dispatch U/I code to main thread using dispach_async in the handler.
    uMM.startAccelerometerUpdatesToQueue( NSOperationQueue() )  p, _ in
        if p != nil 
            println(
                abs( p.acceleration.y ) < abs( p.acceleration.x )
                ?   p.acceleration.x > 0 ? "Right"  :   "Left"
                :   p.acceleration.y > 0 ? "Down"   :   "Up"
            )
        
    


override func
viewDidDisappear( p: Bool ) 
    super.viewDidDisappear( p )
    uMM.stopAccelerometerUpdates()

【讨论】:

一个惊人的简单答案......完美运行。谢谢! 很好的答案,因为 UIAccelerometer 现在已被弃用,不能在 Swift 中使用。 如果您将手机正面朝上或朝下放在桌子上,您如何知道方向?它的 x 和 y 值都为 0。我认为这可以通过陀螺仪传感器数据中的一些数据来解决。 加速结构有z值。您只需要检查 z 的符号即可。【参考方案2】:

该功能是正确的。如果它总是返回设备方向,即使它被锁定,方向更改通知也会触发。这会破坏锁定的目的。

要回答您的问题,如果不使用私有 API,就无法从加速度计读取原始值。

编辑:

查看文档后,UIAccelerometer 类似乎提供了此数据,即使在方向被锁定时也是如此。此更改适用于 ios 4 及更高版本。即使您可以使用这些数据,您仍然需要对其进行处理以确定方向。这不是一件容易的事,因为您需要不断监控更改并将它们与旧值进行比较。

另外,请查看 this guide 以处理运动事件。这可能会为您提供另一种确定方向的途径。

【讨论】:

+1 作为一个小问题,您可以简单地监控加速度计并尝试自己确定方向而不使用私有 API,但这有点离题。 (锁定的意思毕竟是锁定的。) 我不明白为什么你不能监控加速度计,它与屏幕方向系统分开工作。 我不同意你的第一句话。设备方向通知是通知设备的方向而不是接口。理论上,无论旋转锁定如何,都应该通知它。 这个游戏:itunes.apple.com/us/app/superbrothers-sword-sworcery/… 以一种很好的方式无视方向锁定。【参考方案3】:

设置您的视图控制器或任何支持 UIAccelerometerProtocol 的东西,并开始监听变化(您可以将其设置为 10 hz)。

#define kAccelerometerFrequency        10.0 //Hz
-(void)viewDidAppear:(BOOL)animated 
    DLog(@"viewDidAppear");
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    UIAccelerometer* a = [UIAccelerometer sharedAccelerometer];
    a.updateInterval = 1 / kAccelerometerFrequency;
    a.delegate = self;


-(void)viewWillDisappear:(BOOL)animated 
    DLog(@"viewWillDisappear");
    UIAccelerometer* a = [UIAccelerometer sharedAccelerometer];
    a.delegate = nil;
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];


#ifdef DEBUG
+(NSString*)orientationToText:(const UIInterfaceOrientation)ORIENTATION 
    switch (ORIENTATION) 
        case UIInterfaceOrientationPortrait:
            return @"UIInterfaceOrientationPortrait";
        case UIInterfaceOrientationPortraitUpsideDown:
            return @"UIInterfaceOrientationPortraitUpsideDown";
        case UIInterfaceOrientationLandscapeLeft:
            return @"UIInterfaceOrientationLandscapeLeft";
        case UIInterfaceOrientationLandscapeRight:
            return @"UIInterfaceOrientationLandscapeRight";
    
    return @"Unknown orientation!";

#endif

#pragma mark UIAccelerometerDelegate
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
    UIInterfaceOrientation orientationNew;
    if (acceleration.x >= 0.75) 
        orientationNew = UIInterfaceOrientationLandscapeLeft;
    
    else if (acceleration.x <= -0.75) 
        orientationNew = UIInterfaceOrientationLandscapeRight;
    
    else if (acceleration.y <= -0.75) 
        orientationNew = UIInterfaceOrientationPortrait;
    
    else if (acceleration.y >= 0.75) 
        orientationNew = UIInterfaceOrientationPortraitUpsideDown;
    
    else 
        // Consider same as last time
        return;
    

    if (orientationNew == orientationLast)
        return;

    NSLog(@"Going from %@ to %@!", [[self class] orientationToText:orientationLast], [[self class] orientationToText:orientationNew]);
    orientationLast = orientationNew;

#pragma mark -

您需要将UIInterfaceOrientation orientationLast 定义为成员变量并设置好。

【讨论】:

抱歉打扰了,很棒的代码。您将如何使用陀螺仪检查 UIDeviceOrientationFaceDown / UIDeviceOrientationFaceUp ...? 这是很好的代码,但我认为你混淆/混淆了 deviceOrientation 和 interfaceOrientation 之间的差异是一种耻辱..【参考方案4】:

处理所有 6 个方向

虽然我们不经常关心 FaceUp / FaceDown 方向,但它们仍然很重要。

将它们考虑在内会导致对方向变化的敏感度更高,而将它们排除在外会导致亚稳态和滞后。

这是我的处理方式-

- (void)startMonitoring

    [self.motionManager startAccelerometerUpdatesToQueue:self.opQueue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) 

        if (error != nil)
        
            NSLog(@"Accelerometer error: %@", error);
        
        else
        
            float const threshold = 40.0;

            BOOL (^isNearValue) (float value1, float value2) = ^BOOL(float value1, float value2)
            
                return fabsf(value1 - value2) < threshold;
            ;

            BOOL (^isNearValueABS) (float value1, float value2) = ^BOOL(float value1, float value2)
            
                return isNearValue(fabsf(value1), fabsf(value2));
            ;

            float yxAtan = (atan2(accelerometerData.acceleration.y, accelerometerData.acceleration.x)) * 180 / M_PI;
            float zyAtan = (atan2(accelerometerData.acceleration.z, accelerometerData.acceleration.y)) * 180 / M_PI;
            float zxAtan = (atan2(accelerometerData.acceleration.z, accelerometerData.acceleration.x)) * 180 / M_PI;

            UIDeviceOrientation orientation = self.orientation;

            if (isNearValue(-90.0, yxAtan) && isNearValueABS(180.0, zyAtan))
            
                orientation = UIDeviceOrientationPortrait;
            
            else if (isNearValueABS(180.0, yxAtan) && isNearValueABS(180.0, zxAtan))
            
                orientation = UIDeviceOrientationLandscapeLeft;
            
            else if (isNearValueABS(0.0, yxAtan) && isNearValueABS(0.0, zxAtan))
            
                orientation = UIDeviceOrientationLandscapeRight;
            
            else if (isNearValue(90.0, yxAtan) && isNearValueABS(0.0, zyAtan))
            
                orientation = UIDeviceOrientationPortraitUpsideDown;
            
            else if (isNearValue(-90.0, zyAtan) && isNearValue(-90.0, zxAtan))
            
                orientation = UIDeviceOrientationFaceUp;
            
            else if (isNearValue(90.0, zyAtan) && isNearValue(90.0, zxAtan))
            
                orientation = UIDeviceOrientationFaceDown;
            

            if (self.orientation != orientation)
            
                dispatch_async(dispatch_get_main_queue(), ^

                    [self orientationDidChange:orientation];
                );
            
        
    ];

此外,我添加了 threshold 值 40.0(而不是 45.0)。这使得变化不那么敏感,防止了拐点处的滞后。

如果您只想对主要 4 个方向的变化做出反应,就这样做

if (UIDeviceOrientationIsPortrait(orientation) || UIDeviceOrientationIsLandscape(orientation))

     // Do something

【讨论】:

【参考方案5】:

当设备方向被锁定时,UIAccelerometer 类继续工作。您必须制定自己的方法将其变量转换为方向值,但它不应该特别复杂。

试一试 Apple 的 AcceleromoterGraph 示例应用,看看加速度计在不同方向上输出的值。

【讨论】:

【参考方案6】:

我使用 coremotion 的解决方案,即使设备锁定了他的方向,它也可以工作。

    let motionManager: CMMotionManager = CMMotionManager()

关于加载方法

motionManager.deviceMotionUpdateInterval = 0.01
    if motionManager.accelerometerAvailable
        let queue = NSOperationQueue()
        motionManager.startAccelerometerUpdatesToQueue(queue, withHandler:
            data, error in

                guard let data = data else
                    return
                
                let angle = (atan2(data.acceleration.y,data.acceleration.x))*180/M_PI;

                print(angle)
                if(fabs(angle)<=45)
                    self.orientation = AVCaptureVideoOrientation.LandscapeLeft
                    print("landscape left")
                else if((fabs(angle)>45)&&(fabs(angle)<135))

                    if(angle>0)
                        self.orientation = AVCaptureVideoOrientation.PortraitUpsideDown
                        print("portrait upside Down")


                    else
                        self.orientation = AVCaptureVideoOrientation.Portrait
                        print("portrait")

                    
                else
                    self.orientation = AVCaptureVideoOrientation.LandscapeRight
                    print("landscape right")

                


            
        )
     else 
        print("Accelerometer is not available")
    

希望对你有帮助。

【讨论】:

【参考方案7】:

大部分答案都是使用加速度计,即整体加速度 = 用户+重力。

但要获得设备方向,使用重力加速度会更准确。当用户在特定方向移动时,使用重力将防止边缘情况。要访问重力,我们必须改用startDeviceMotionUpdates API。

let motionManager = CMMotionManager()
motionManager.startDeviceMotionUpdates(to: OperationQueue())  (data, error) in
    guard let gravity = data?.gravity else  return 

    let newDeviceOrientation: UIDeviceOrientation
    if abs(gravity.y) < abs(gravity.x) 
        newDeviceOrientation = gravity.x > 0 ? .landscapeRight : .landscapeLeft
     else 
        newDeviceOrientation = gravity.y > 0 ? .portraitUpsideDown : .portrait
    

【讨论】:

【参考方案8】:

使用CMMotionManager 可能会有所帮助,但不是上述方式。上述逻辑不是一个稳定的逻辑。我进行了彻底的测试,发现通过查看acceleration.x/y/z 的值并不能帮助确定方向。

相反,我有一种方法可以找到 WRT 角度的方向,即 float angle = (atan2(accelerometerData.acceleration.y,accelerometerData.acceleration.x))*180/M_PI;

对于方向,- if(fabs(angle<=45)currOrientation=UIDeviceOrientationLandscapeRight; else if((fabs(angle)>45)&&(fabs(angle)<135))currOrientation=((angle>0)?UIDeviceOrientationPortraitUpsideDown:UIDeviceOrientationPortrait); else currOrientation = UIDeviceOrientationLandscapeLeft;

这对某人来说可能会派上用场,尽管这并不能帮助我找到其他 2 个方向,即UIDeviceOrientationFaceUpUIDeviceOrientationFaceDown

【讨论】:

【参考方案9】:

在这里使用 Satachito 的最佳答案是代码,它还将检测设备是正面朝上还是正面朝下

import CoreMotion

var mm: CMMotionManager!

init() 
    self.mm = CMMotionManager()
    self.mm.accelerometerUpdateInterval = 0.2


public func startOrientationUpdates() 
    //  Using main queue is not recommended. So create new operation queue and pass it to startAccelerometerUpdatesToQueue.
    //  Dispatch U/I code to main thread using dispach_async in the handler.
    self.mm.startAccelerometerUpdates( to: OperationQueue() )  p, _ in
        if let p = p 
            if(p.acceleration.x > -0.3 && p.acceleration.x < 0.3 && p.acceleration.z < -0.95) 
                print("face up")
            
            else if(p.acceleration.x > -0.3 && p.acceleration.x < 0.3 && p.acceleration.z > 0.95) 
                print("face down")
            
            else 
                print(
                    abs( p.acceleration.y ) < abs( p.acceleration.x )
                        ?   p.acceleration.x > 0 ? "Right"  :   "Left"
                        :   p.acceleration.y > 0 ? "Down"   :   "Up"
                )
            

        
    


public func endOrientationUpdates() 
    self.mm.stopAccelerometerUpdates()

【讨论】:

【参考方案10】:

以下是检测设备旋转并返回 UIDeviceOrientation 的示例。 此解决方案使用 CoreMotion,适用于所有情况。

示例

let orientationManager = APOrientationManager()
orientationManager.delegate = self
/// start detect rotation
orientationManager.startMeasuring()

/// get current interface orientation
let orientation = orientationManager.currentInterfaceOrientation()
print(orientation.rawValue)

/// stop detect rotation
orientationManager.stopMeasuring()
orientationManager.delegate = nil

符合委托

extension ViewController: APOrientationManagerDelegate 
    func didChange(deviceOrientation: UIDeviceOrientation) 
        /// update UI in main thread
    

APOrientationManager.swift

import Foundation
import CoreMotion
import AVFoundation
import UIKit

protocol APOrientationManagerDelegate: class 
    func didChange(deviceOrientation: UIDeviceOrientation)


class APOrientationManager 

    private let motionManager = CMMotionManager()
    private let queue = OperationQueue()
    private var deviceOrientation: UIDeviceOrientation = .unknown
    weak var delegate: APOrientationManagerDelegate?

    init() 
        motionManager.accelerometerUpdateInterval = 1.0
        motionManager.deviceMotionUpdateInterval = 1.0
        motionManager.gyroUpdateInterval = 1.0
        motionManager.magnetometerUpdateInterval = 1.0
    

    func startMeasuring() 
        guard motionManager.isDeviceMotionAvailable else 
            return
        
        motionManager.startAccelerometerUpdates(to: queue)  [weak self] (accelerometerData, error) in
            guard let strongSelf = self else 
                return
            
            guard let accelerometerData = accelerometerData else 
                return
            

            let acceleration = accelerometerData.acceleration
            let xx = -acceleration.x
            let yy = acceleration.y
            let z = acceleration.z
            let angle = atan2(yy, xx)
            var deviceOrientation = strongSelf.deviceOrientation
            let absoluteZ = fabs(z)

            if deviceOrientation == .faceUp || deviceOrientation == .faceDown 
                if absoluteZ < 0.845 
                    if angle < -2.6 
                        deviceOrientation = .landscapeRight
                     else if angle > -2.05 && angle < -1.1 
                        deviceOrientation = .portrait
                     else if angle > -0.48 && angle < 0.48 
                        deviceOrientation = .landscapeLeft
                     else if angle > 1.08 && angle < 2.08 
                        deviceOrientation = .portraitUpsideDown
                    
                 else if z < 0 
                    deviceOrientation = .faceUp
                 else if z > 0 
                    deviceOrientation = .faceDown
                
             else 
                if z > 0.875 
                    deviceOrientation = .faceDown
                 else if z < -0.875 
                    deviceOrientation = .faceUp
                 else 
                    switch deviceOrientation 
                    case .landscapeLeft:
                        if angle < -1.07 
                            deviceOrientation = .portrait
                        
                        if angle > 1.08 
                            deviceOrientation = .portraitUpsideDown
                        
                    case .landscapeRight:
                        if angle < 0 && angle > -2.05 
                            deviceOrientation = .portrait
                        
                        if angle > 0 && angle < 2.05 
                            deviceOrientation = .portraitUpsideDown
                        
                    case .portraitUpsideDown:
                        if angle > 2.66 
                            deviceOrientation = .landscapeRight
                        
                        if angle < 0.48 
                            deviceOrientation = .landscapeLeft
                        
                    case .portrait:
                        if angle > -0.47 
                            deviceOrientation = .landscapeLeft
                        
                        if angle < -2.64 
                            deviceOrientation = .landscapeRight
                        
                    default:
                        if angle > -0.47 
                            deviceOrientation = .landscapeLeft
                        
                        if angle < -2.64 
                            deviceOrientation = .landscapeRight
                        
                    
                
            
            if strongSelf.deviceOrientation != deviceOrientation 
                strongSelf.deviceOrientation = deviceOrientation
                strongSelf.delegate?.didChange(deviceOrientation: deviceOrientation)
            
        
    

    func stopMeasuring() 
        motionManager.stopAccelerometerUpdates()
    

    func currentInterfaceOrientation() -> AVCaptureVideoOrientation 
        switch deviceOrientation 
        case .portrait:
            return .portrait
        case .landscapeRight:
            return .landscapeLeft
        case .landscapeLeft:
            return .landscapeRight
        case .portraitUpsideDown:
            return .portraitUpsideDown
        default:
            return .portrait
        
    

【讨论】:

以上是关于iOS设备方向无视方向锁定的主要内容,如果未能解决你的问题,请参考以下文章

iOS 应用程序方向旋转锁定/解锁由应用程序的配置

如何为 React Native 锁定设备方向

即使设备已锁定方向,如何进行方向检测

如何在纵向锁定设备时获取设备方向

当活动方向被锁定时检测设备方向

处理 ios 设备方向