iosobjective-cuiviewcontrolleruideviceorientation

UIViewController animations wait until animation complete


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;
    }
}

Solution

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