3
votes

So, I've got a project where I need to force an orientation change when a user presses a button. I've created a sample app on github to demonstrate the issue.

@interface DefaultViewController () {
    UIInterfaceOrientation _preferredOrientation;
}

Some rotation handling bits

- (BOOL)shouldAutorotate {
    return YES;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return _preferredOrientation;
}

And a toggle

- (IBAction)toggleButtonPressed:(id)sender {
    _preferredOrientation = UIInterfaceOrientationIsPortrait(_preferredOrientation)
        ? UIInterfaceOrientationLandscapeRight
        : UIInterfaceOrientationPortrait;

    [self forceOrientationChange];
}


- (void)forceOrientationChange
{
    UIViewController *vc = [[UIViewController alloc] init];
    [self presentViewController:vc animated:NO completion:nil];

    [UIView animateWithDuration:.3 animations:^{
        [vc dismissViewControllerAnimated:NO completion:nil];
    } completion:nil];
}

So, this seems to work fine when you just press the toggle button, it changes the orientation as expected. But when I start changing the actual orientation of the device, there is an issue.

Steps to reproduce the issue:

  1. Open app in portrait orientation
  2. Press the button to force the orientation change to landscape (Keeping the actual device in portrait)
  3. Press the button again to force the rotation back to portrait (Still keeping the device in portrait)
  4. Rotate the actual device to landscape without pressing the button

The result is that the view does not rotate to landscape, but the status bar does.

Forced orientation change issue

Any help would be greatly appreciated!

3

3 Answers

6
votes

I was able to fix this by adding the following line before presenting and dismissing the view controller:

[UIViewController attemptRotationToDeviceOrientation]; 

It's hard to say exactly why this works. This call asks attempts to match the interfaceOrientation to deviceOrientation by calling shouldAutorotateToInterfaceOrientation: and the subsequent rotation methods if YES is returned.

It seems the interface and device orientations were getting stuck out of sync due to the workaround required to force an interface orientation in spite of the device orientation. Still shouldn't happen though in my opinion, as the workaround is still a legitimate use of the provided methods. It could be a bug in Apple's rotation/orientation stack.

Complete method:

- (void)forceOrientationChange
{
    UIViewController *vc = [[UIViewController alloc] init];

    [UIViewController attemptRotationToDeviceOrientation]; /* Add this line */

    [self presentViewController:vc animated:NO completion:nil];

    [UIView animateWithDuration:.3 animations:^{
        [vc dismissViewControllerAnimated:NO completion:nil];
    } completion:nil];
}
1
votes

When we are talking about orientation, they are 2 things that come into the picture:

Device Orientation Interface Orientation As its clear by the name only, Device orientation tells, in which orientation device is, and Interface orientation says in which orientation your app is presenting its interface.

Here what you are doing is, your app is supporting all orientation. You must have check marked all orientations in project.

Now when you are changing orientation of device from portrait to landscape, when you have set interfaceOrientation to be in portrait mode programmatically, this is what happens. As device orientation is changes, orientation of your status bar also changes. But as you have restricted interface of your app to be in portrait orientation, its not changing its orientation..

So this is what you can do:

  1. Uncheck landscape orientation support for your app & check if problem persists.
  2. Let me know what happened when you followed first step :)
0
votes

Use this code:

UIApplication *application = [UIApplication sharedApplication];
[application setStatusBarOrientation:UIDeviceOrientationLandscapeLeft 
                            animated:YES];
[[UIDevice currentDevice] setOrientation:UIDeviceOrientationLandscapeLeft];
[super updateViewConstraints];
[self.view updateConstraints];