4

I'm using this observer: UIDeviceOrientationDidChangeNotification to detect when user is changing the device orientation. When the orientation changed to landscape I present a new UIViewController or dismiss this UIViewController when he changes it back to portrait.

My problem starts when the user start rotating the device number of times and fast, then the application goes crazy until I get this error:

Warning: Attempt to present on whose view is not in the window hierarchy!`.

What is the best possible way to wait until the animation is over and then change the rotation?

This is what I'm using on the Presenting view controller:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self beginDeviceOrientationListener];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)beginDeviceOrientationListener
{
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
}

- (void)orientationChanged:(NSNotification *)notification
{
    UIDevice *device = notification.object;
    switch (device.orientation)
    {
        case UIDeviceOrientationLandscapeLeft:
        case UIDeviceOrientationLandscapeRight:
        {
            TheViewControllerToPresent *viewController = [[TheViewControllerToPresent alloc] init];
            [self presentViewController:viewController animated:YES completion:nil];
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeRight] forKey:@"orientation"];
            [[UIApplication sharedApplication] setStatusBarOrientation:[[[UIDevice currentDevice] valueForKey:@"orientation"] integerValue] animated:YES];
            [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
        }
            break;

        default:
            break;
    }
}

This is what I'm using on the Presented view controller:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self beginDeviceOrientationListener];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)beginDeviceOrientationListener
{
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
}

- (void)orientationChanged:(NSNotification *)notification
{
    UIDevice *device = notification.object;
    switch (device.orientation)
    {
        case UIDeviceOrientationPortrait:
        {
            [self dismissViewControllerAnimated:YES completion:nil];
            [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationPortrait] forKey:@"orientation"];
            [[UIApplication sharedApplication] setStatusBarOrientation:[[[UIDevice currentDevice] valueForKey:@"orientation"] integerValue] animated:YES];
        }
            break;
        default:
            break;
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
ytpm
  • 4,962
  • 6
  • 56
  • 113
  • Why are you setting `[UIDevice orientation]`? what kind of terrible hack is it? – Sulthan May 07 '15 at 13:46
  • Well, it's actually working pretty good, why a hack? what should I use instead? – ytpm May 07 '15 at 14:02
  • `[UIDevice orientation]` is readonly for a reason. The hack is that you are accessing it using reflection (`setValue:`), circumventing the `readonly` status. If you don't want to use the default rotation support that controllers have, you can just use `transform` on the view of the presented controller. – Sulthan May 07 '15 at 14:06

1 Answers1

3

The most simplest solution that I am using myself is stopping the user from making any changes while the animation is in progress.

This is done by adding the following code when the animation starts:

[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];

and to the completion handler:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];

The user can rotate the device but the events won't be generated during the animation so the animations won't collide. However, when the animation ends, you should explicitly check the orientation:

UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

to see whether you should start another animation or not.

Sulthan
  • 128,090
  • 22
  • 218
  • 270