iOS设备方向不受方向锁限制

34

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

[UIDevice currentDevice].orientation

只要设备没有锁定方向,这个方法就有效。但是如果设备被锁定了,它总是会响应锁定的方向而不是实际设备的方向。

是否有一种高级方法可以获取实际的设备方向?

10个回答

41

另外,您可以使用CoreMotion

方向检测算法:

  • 如果abs(y) < abs(x),则您的iPhone处于横向位置,请查看x的符号以检测右侧或左侧

  • 否则,您的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()
}

一个令人惊叹的简单答案...完美地运行了。谢谢! - Ashley Mills
很好的回答,因为UIAccelerometer现在已经被弃用,无法在Swift中使用。 - Michael Gaylord
如果您把手机正面朝上或者朝下放在桌子上,如何知道它的方向?此时x和y两个值都为0。我认为可以通过陀螺仪传感器的数据来解决这个问题。 - FlySoFast
加速结构具有z值。您只需要检查z的符号即可。 - Satachito
我认为你可以检查abs(p.acceleration.z)是否大于abs(p.acceleration.x)和abs(p.acceleration.y),然后设备就是面朝上或面朝下。然后检查p.acceleration.z的符号,就可以知道它是面朝上还是面朝下。非常干净利落的答案! - Adam Freeman

10

这个功能是正确的。如果它总是返回设备方向,即使它被锁定,那么方向改变通知将会触发,这将违背锁定的目的。

回答你的问题,没有办法在不使用私有API的情况下读取加速度计的原始值。

编辑:

经过审核文档后,似乎UIAccelerometer类提供了此数据,即使方向被锁定。这个更改应用于iOS 4及以上版本。即使你可以使用这些数据,你仍然需要对其进行处理以确定方向。这不是一个简单的任务,因为你需要不断监测变化并将它们与旧值进行比较。

此外,请查看此指南以处理运动事件。这可能为您提供另一种确定方向的方法。


1
作为一个小点,你可以简单地监控加速度计并尝试自己确定方向,而不使用私有API,但这有点离题。(毕竟锁定就是锁定。) - John Parker
1
我不明白为什么你不能监视加速度计,它与屏幕方向系统分开工作。 - BastiBen
2
我不同意你的第一句话。设备方向通知是通知设备的方向而不是界面。理论上,无论旋转锁定如何,都应该通知它。 - Jesse Armand
1
这个游戏:http://itunes.apple.com/us/app/superbrothers-sword-sworcery/id424912055?mt=8 非常好地、合理地忽略了方向锁定。 - Jonny

8

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

#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? - Shai Mishali
这是好的代码,但我认为你混淆了deviceOrientation和interfaceOrientation之间的差异,这有点可惜。 - Jef

6

处理所有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)。这样可以使变化的敏感度降低,防止在拐点处产生迟滞。
如果您只想对主要的四个方向的变化做出反应,只需这样做。
if (UIDeviceOrientationIsPortrait(orientation) || UIDeviceOrientationIsLandscape(orientation))
{
     // Do something
}

5

当设备方向被锁定时,UIAccelerometer类仍然可以正常工作。您需要自己想出将其变量转换为方向值的方法,但这不应该特别复杂。

可以尝试使用苹果的AccelerometerGraph示例应用程序,查看加速度计在不同方向输出的值。


3

我使用CoreMotion提供的解决方案,即使设备方向被锁定,也可以正常工作。

    let motionManager: CMMotionManager = CMMotionManager()

在 did load 方法中

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

希望能对您有所帮助。

3

大部分答案使用加速度计,这是总加速度=用户加重力。

但是为了获得设备方向,使用重力加速度更准确。使用重力将防止用户沿特定方向移动的边缘情况发生。要访问重力,我们必须使用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
    }
}

2

这里是一个检测设备旋转并返回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
        }
    }
}

1

在这里使用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()
}

1
使用CMMotionManager可能会有所帮助,但不是上述方法。上述逻辑不稳定。我已经进行了彻底的测试,并发现通过查看acceleration.x/y/z的值不能帮助确定方向。
相反,我找到了一种找到相对于角度的方向的方法,即 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; 这可能对某些人很方便,尽管这并不能帮助我找到另外两个方向,即UIDeviceOrientationFaceUpUIDeviceOrientationFaceDown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接