Aigori Aigori - 2 months ago 30
iOS Question

How to change UI orientation without fooling iOS

My goal is changing lock/unlock orientation using button. In lock mode, orientation can be switched by button, and in unlock mode, orientation is determined by sensor.

I tried to achieve this with

[[UIDevice currentDevice] setValue:[NSNumber numberWithInt:orientation] forKey:@"orientation"]
, but this code has problem.

Assume that currently my app is 'Lock mode, portrait UI, portrait device'. Then I rotate device to landscape left, and unlock orientation. I expected
[[UIDevice currentDevice] orientation]
should be
UIDeviceOrientationLandscapeLeft
. But the value was
UIDeviceOrientationPortrait
though the real device is in landscape!

I tried also notification center with code below.

[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onDeviceOrientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];


But this code does not working properly. If the device is in landscape and UI orientation is set to portrait by
[[UIDevice currentDevice] setValue:[NSNumber numberWithInt:UIDeviceOrientationPortrait]
,
onDeviceOrientationChanged
is not called when I rotate device to portrait. I think device's orientation value is already set to portrait (by my code, not sensor).

ps. Of course, I checked Required full screen option.

EDIT: It would be also good answer to know how to get device's real orientation which is not affected by
[[UIDevice currentDevice] setValue]
.

Answer

I solved this problem using MotionManager. When I lock (or change) orientation, [[UIDevice currentDevice] setValue:[NSNumber numberWithInt:orientation] forKey:@"orientation"] enough as usual.
However, when I unlock orientation, I refreshed orientation using MotionManager with below code.

CMMotionManager motionManager = [[CMMotionManager alloc] init];

[motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
    withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        [motionManager stopAccelerometerUpdates];

        CMAcceleration acceleration = accelerometerData.acceleration;
        UIInterfaceOrientation orientation;
        if (acceleration.x >= 0.75) {
            orientation = UIInterfaceOrientationLandscapeLeft;
        } else if (acceleration.x <= -0.75) {
            orientation = UIInterfaceOrientationLandscapeRight;
        } else if (acceleration.y <= -0.75) {
            orientation = UIInterfaceOrientationPortrait;
        } else if (acceleration.y >= 0.75) {
            orientation = UIInterfaceOrientationPortraitUpsideDown;
        } else {
            return;
        }
        [[UIDevice currentDevice] setValue:[NSNumber numberWithInt:orientation]
                            forKey:@"orientation"];
        [UINavigationController attemptRotationToDeviceOrientation];
    }];

Of course, you should aware of supportedInterfaceOrientations and shouldAutorotate methods.