iOS device orientation disregarding orientation lock

Also you can use CoreMotion

Orientation detection algorithm:

  • if abs( y ) < abs( x ) your iPhone is in landscape position, look sign of x to detect right or left

  • else your iPhone is in portrait position, look sign of y to detect up or upside-down.

  • If you are interested in face-up or down, look value of 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()
}

Set up your view controller or whatever to support the UIAccelerometerProtocol, and start listening for changes (you can set it to 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.


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.

Edit:

After reviewing the documentation, it seems that the UIAccelerometer class provides this data, even when the orientation is locked. This change was applied in iOS 4 and above. 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.


Handling all 6 orientations

Though we don't often care about FaceUp / FaceDown orientations, they're still important.

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). 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

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