简体   繁体   English

iOS设备方向无视方向锁定

[英]iOS device orientation disregarding orientation lock

I want to query the orientation the iPhone is currently in. Using我想查询 iPhone 当前所处的方向。使用

[UIDevice currentDevice].orientation

works as long as the device isn't orientation-locked.只要设备没有方向锁定就可以工作。 If it is locked, however, it always responds with the locked orientation, not with the actual orientation of the device.但是,如果它被锁定,它总是以锁定的方向响应,而不是设备的实际方向。

Is there a high-level way to get the actual device orientation?是否有获得实际设备方向的高级方法?

Also you can use CoreMotion你也可以使用 CoreMotion

Orientation detection algorithm:方向检测算法:

  • if abs( y ) < abs( x ) your iPhone is in landscape position, look sign of x to detect right or left如果 abs( y ) < abs( x ) 你的 iPhone 处于横向位置,看 x 的标志来检测左右

  • else your iPhone is in portrait position, look sign of y to detect up or upside-down.否则你的 iPhone 处于纵向位置,看 y 的标志来检测向上或倒置。

  • If you are interested in face-up or down, look value of z.如果您对面朝上或朝下感兴趣,请查看 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()
}

That functionality is correct.该功能是正确的。 If it always returned the device orientation, even if it was locked, the orientation changed notifications would fire.如果它始终返回设备方向,即使它被锁定,也会触发方向更改通知。 This would defeat the purpose of the lock.这将违背锁定的目的。

To answer your question, there is no way to read the raw values from the accelerometer, without using private APIs. 要回答您的问题,如果不使用私有 API,就无法从加速度计读取原始值。

Edit:编辑:

After reviewing the documentation, it seems that the UIAccelerometer class provides this data, even when the orientation is locked.查看文档后,即使方向被锁定,UIAccelerometer 类似乎也提供了此数据。 This change was applied in iOS 4 and above.此更改适用于 iOS 4 及更高版本。 Even though you can use this data, you still need to process it to determine the orientation.即使您可以使用这些数据,您仍然需要对其进行处理以确定方向。 This is not an easy task as you need to monitor the changes constantly and compare them to older values.这不是一项容易的任务,因为您需要不断监视更改并将它们与旧值进行比较。

Also, take a look at this guide for handling motion events.另外,请查看本指南以处理运动事件。 This may provide you with another route to determining the orientation.这可能会为您提供另一种确定方向的途径。

Set up your view controller or whatever to support the UIAccelerometerProtocol, and start listening for changes (you can set it to 10 hz).设置你的视图控制器或任何支持 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 -

You need to define UIInterfaceOrientation orientationLast as a member variable and you're set.您需要将UIInterfaceOrientation orientationLast定义为成员变量并进行设置。

Handling all 6 orientations处理所有 6 个方向

Though we don't often care about FaceUp / FaceDown orientations, they're still important.尽管我们通常不关心FaceUp / FaceDown方向,但它们仍然很重要。

Taking them into account leads to a much more appropriate sensitivity for orientation changes, while leaving them out can lead to metastability & hysteresis.将它们考虑在内会导致对方向变化的更合适的敏感性,而将它们排除在外会导致亚稳态和滞后。

Here's how I handled it -我是这样处理的——

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

Additionally, I've added a threshold value of 40.0 (instead of 45.0).此外,我还添加了 40.0(而不是 45.0)的threshold值。 This makes changes less sensitive, preventing hysteresis at inflection points.这使得更改不那么敏感,防止在拐点处出现滞后。

If you only want to react to changes of the main 4 orientations, just do this如果您只想对主要 4 个方向的变化做出反应,请执行以下操作

if (UIDeviceOrientationIsPortrait(orientation) || UIDeviceOrientationIsLandscape(orientation))
{
     // Do something
}

The UIAccelerometer class continues to function when the device orientation is locked.当设备方向被锁定时,UIAccelerometer 类继续运行。 You'll have to work out your own methods of turning its variables into orientation values, but it shouldn't be especially complicated.您必须自己制定将其变量转换为方向值的方法,但这不应该特别复杂。

Have a play with Apple's AcceleromoterGraph sample app to see what values the accelerometer outputs in different orientations.试试 Apple 的 AcceleromoterGraph 示例应用程序,看看加速度计在不同方向输出的值。

my solution using coremotion,it work even when the device has his orientation locked.我的解决方案使用 coremotion,即使设备的方向被锁定,它也能工作。

    let motionManager: CMMotionManager = CMMotionManager()

on the did load method关于 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")
    }

hope it helps.希望它有帮助。

Most of the answers are using accelerometer, which is the overall acceleration = user + gravity.大部分答案都是用加速度计,也就是整体加速度=用户+重力。

But to get a device orientation, it is more accurate to use the gravity acceleration .但是要获得设备方向,使用重力加速度更准确。 Using gravity will prevent the edge case when user moves in a particular direction.当用户沿特定方向移动时,使用重力将防止边缘情况。 To access the gravity, we have to use startDeviceMotionUpdates API instead.要访问重力,我们必须改用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
    }
}

Use of CMMotionManager may help, but not the above way.使用CMMotionManager可能会有所帮助,但不是上述方式。 The above logic is not a stable one.上述逻辑不是一个稳定的逻辑。 I have tested throughly and found that by seeing the values of acceleration.x/y/z are not helping to determine the orientation.我已经彻底测试过,发现通过查看acceleration.x/y/z .x acceleration.x/y/z并不能帮助确定方向。

Instead, I got a way to find the orientation WRT the angle ie float angle = (atan2(accelerometerData.acceleration.y,accelerometerData.acceleration.x))*180/M_PI;相反,我找到了一种方法来找到方向 WRT 角度即float angle = (atan2(accelerometerData.acceleration.y,accelerometerData.acceleration.x))*180/M_PI;

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

This might come handy for someone, though this doesn't help me to find 2 other orientations ie UIDeviceOrientationFaceUp & UIDeviceOrientationFaceDown .这对某人来说可能会派上用场,尽管这并不能帮助我找到 2 个其他方向,即UIDeviceOrientationFaceUpUIDeviceOrientationFaceDown

Using Satachito's great answer here is code which will also detect if the device is face up or face down在这里使用 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()
}

Here is an example of detect device rotation and return UIDeviceOrientation.这是检测设备旋转并返回 UIDeviceOrientation 的示例。 This solution using CoreMotion and works in all cases.此解决方案使用 CoreMotion,适用于所有情况。

Example示例

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

conform delegate符合委托

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

APOrientationManager.swift AOrientationManager.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
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM