2
votes

I've tried to make a custom segue to simulate the old segue push from iOS6, since the new push segue doesn't work very well with my app.

-(void)perform {

    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];

    CATransition* transition = [CATransition animation];
    transition.duration = 1.5;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
    transition.subtype = kCATransitionFromRight; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom



    [sourceViewController.navigationController.view.layer addAnimation:transition
                                                                forKey:kCATransition];

    [sourceViewController.navigationController pushViewController:destinationController animated:NO];

}

Everything is embedded in a navigation controller. I have marked Extend Edges "Under Top Bars" and "Unter Bottom Bars". And I have a custom background color of the view in both view controllers (source and destination). During the transition between these two view controllers the navigation bar starts flickering between white and my custom background color of both view controllers. Any idea why?

1
it's probably because you're pushing the view controller not animated while doing your animation. Try pushing first and then doing your animation - Patrick Goley
I don't really know how you mean that? Could you give an example? - MichiZH
I think you can try answer of Patrick he is suggesting to put [sourceViewController.navigationController pushViewController:destinationController animated:NO]; code before animation code.. - NSCry
@MichiZH I added an answer with working code and an example project, pls check if it's ok for the bounty ;-) - LombaX

1 Answers

4
votes

The problem in your code is this:

when you trigger the pushViewController method, the sourceViewController is removed from the navigation stack immediately, and the destinationViewController appears. This causes a problem in the animation (the problem exists even on iOS 6, causing a different problem, a "black fade"). If you still want to use pushViewController (which is easier because you don't need to rebuild the navigation logic), you have to do it as suggested by Apple here

Regardless of how you perform the animation, at the end of it, you are responsible for installing the destination view controller (and its views) in the right place so that it can handle events. For example, if you were to implement a custom modal transition, you might perform your animations using snapshot images and then at the end call the presentModalViewController:animated: method (with animations disabled) to set up the appropriate modal relationship between the source and destination view controllers.

You should use a snapshot.

Here your perform method updated:

- (void)perform
{
    UIViewController *source = (UIViewController *) self.sourceViewController;
    UIViewController *destination = (UIViewController *) self.destinationViewController;

    // Swap the snapshot out for the source view controller
    UIWindow *window = source.view.window;
    UIImageView *screenShot = [[UIImageView alloc] initWithImage:[self screenshot]];

    BOOL animsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];

    [window addSubview:screenShot];

    [UIView setAnimationsEnabled:animsEnabled];

    CATransition* transition = [CATransition animation];
    transition.duration = 1.5;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
    transition.subtype = kCATransitionFromRight; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
    [source.navigationController pushViewController:destination animated:NO];
    [destination.navigationController.view.layer addAnimation:transition forKey:kCATransition];


    [UIView animateWithDuration:1.5f animations:^{
        screenShot.frame = CGRectOffset(screenShot.frame, -screenShot.frame.size.width, 0);
    } completion:^(BOOL finished) {
        [screenShot removeFromSuperview];
    }];
}

To do a correct screenshot, it's not enough to use simply the renderInContext method, since the blur of the translucent bar is not managed correctly. I've found another way (copied and pasted from THIS question)

- (UIImage *)screenshot
{
    CGSize imageSize = CGSizeZero;

    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    if (UIInterfaceOrientationIsPortrait(orientation)) {
        imageSize = [UIScreen mainScreen].bounds.size;
    } else {
        imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
    }

    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
        CGContextSaveGState(context);
        CGContextTranslateCTM(context, window.center.x, window.center.y);
        CGContextConcatCTM(context, window.transform);
        CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y);
        if (orientation == UIInterfaceOrientationLandscapeLeft) {
            CGContextRotateCTM(context, M_PI_2);
            CGContextTranslateCTM(context, 0, -imageSize.width);
        } else if (orientation == UIInterfaceOrientationLandscapeRight) {
            CGContextRotateCTM(context, -M_PI_2);
            CGContextTranslateCTM(context, -imageSize.height, 0);
        } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
            CGContextRotateCTM(context, M_PI);
            CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
        }
        if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
            [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
        } else {
            [window.layer renderInContext:context];
        }
        CGContextRestoreGState(context);
    }

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

I attach a sample project showing how it works: Link to sample project