19

Using iOS 7's custom view controller transitions, I want to achieve a visual effect similar to Apple's default view controller transition in iOS 7.

(The one where you can slide to pop a view controller off the stack by sliding it from the left to the right, where the top view controller slides off the top of the other with a shadow and the navigation bar shifts.)

enter image description here

I'm having a great deal of difficulty implementing this, though. Most of the tutorials on custom view controllers go with very different effects than the default in order to show what the API is capable off, but I want to replicate this one.

In my subclass for implementing <UIViewControllerAnimatedTransitioning> I have the following code for the interactive animation:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    [transitionContext.containerView addSubview:toViewController.view];
    [transitionContext.containerView addSubview:fromViewController.view];

    fromViewController.view.layer.shadowOffset = CGSizeMake(0.0, 0.0);
    fromViewController.view.layer.shadowColor = [UIColor blackColor].CGColor;
    fromViewController.view.layer.shadowRadius = 5.0;
    fromViewController.view.layer.shadowOpacity = 0.5;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
        CGRect newFrame = fromViewController.view.frame;
        newFrame.origin.x = CGRectGetWidth(fromViewController.view.bounds);

        fromViewController.view.frame = newFrame;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
}

However the shadow code makes it lag tremendously (even if I use the new snapshot methods) and I cannot figure out how to manipulate the navigation bar at all.

Has anyone tried to do something similar to this and are able to provide sample code?

Sample project for testing if you'd like: https://dzwonsemrish7.cloudfront.net/items/43260h1u1T1u010i0V3C/DefaultVCTransition.zip

Credit to objc.io for the base code.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Doug Smith
  • 29,668
  • 57
  • 204
  • 388
  • 3
    Are you trying to replicate what you get for free using a `UINavigationController` but without using a `UINavigationController`? – rmaddy Mar 25 '14 at 03:19
  • 1
    Layer shadows are extremely expensive if your views aren't opaque. Like with the old web view shadows they're probably prerendered images. – Brian Nickel Mar 25 '14 at 14:08
  • @rmaddy I need to replicate the transition for a push as well. Apple only makes it available for a pop. So it will still be used with `UINavigationController`. – Doug Smith Mar 25 '14 at 15:45
  • @BrianNickel Could you elaborate? My view is opaque, at least in nature. Should I be explicitly setting it to be so? – Doug Smith Mar 25 '14 at 15:46
  • If you are using a `UINavigationController` then there is no need to replicate the "pop" since it already does it. – rmaddy Mar 25 '14 at 15:49
  • Yes, but I need to replicate the *push*. And unless I'm able to get it identically (unlikely) that means I'll likely have to update the pop as well to mimic what my push transition ends up being. Hence the question. – Doug Smith Mar 25 '14 at 15:56
  • @DougSmith I'm not sure of everything that sets it off, but layer shadows involve a costly render, calculate alpha, calculate blur process any time it thinks any pixel has changed. There are a few tips on shadow performance on this question: http://stackoverflow.com/questions/9997972/calayer-shadow-causes-a-huge-performance-hit – Brian Nickel Mar 25 '14 at 16:14
  • @BrianNickel Cheers, that worked perfectly. The rasterize option alone made the animation incredibly performant. – Doug Smith Mar 26 '14 at 02:06
  • @DougSmith I’m trying to solve the same problem. Did you find solution? – Vlad Hudnitsky Jul 07 '14 at 19:47
  • I did not, sorry. The Vesper app (http://vesperapp.co) does something similar though, but Brent Simmons uses a custom nav bar: http://inessential.com/2014/06/10/vespers_custom_navbar – Doug Smith Jul 07 '14 at 21:23

3 Answers3

2

Setting the shadowPath greatly increases the performance of this shadow.

Just add this in your animateTransition: method after you have set the shadow properties. This avoids the expensive offscreen rendering that shadow normally causes.

[fromViewController.view.layer setShadowPath:[[UIBezierPath bezierPathWithRect:fromViewController.view.bounds] CGPath]];

I downloaded your sample project and did that, the stutter is now completely gone.

Some info on what this does here.

EDIT:

The answer about manipulating the navigation bar animation is that it doesn't seem like you can. In order to do so you will need to reimplement your own NavigationController-type class from scratch. The transition animation for the navigation bar is done internally by the container view controller (the UINavigationController) and is not surfaced anywhere in the code.

Dima
  • 23,484
  • 6
  • 56
  • 83
0

Have a look at ECSlidingViewController I've used it a couple of times, if you understand how it works, you can easily implement something similar.

If you want to reinvent the wheel, my answer is no use, sorry.

MrHaze
  • 3,786
  • 3
  • 26
  • 47
0

I create the effect using Dynamics I create my own ViewController subclass with the next methods.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil forMultipleDirections:(BOOL)isTwoDirections;
- (void)addVelocityForGesture:(UIPanGestureRecognizer *)recognizer;
- (UIDynamicItemBehavior*) itemBehaviourForView;

Then I added physics and a gesture recognizer in order to handle the paning gesture. to make it look in top just add a shadow offset.

_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.parentViewController.view];
        _gravity = [[UIGravityBehavior alloc] init];
        _gravity.gravityDirection = CGVectorMake(-1.5, 0);
        [_animator addBehavior:_gravity];

        UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.view]];
        [collision addBoundaryWithIdentifier:@1 fromPoint:CGPointMake(0, 0) toPoint:CGPointMake(0, self.parentViewController.view.frame.size.height)];
        [_animator addBehavior:collision];
        [_gravity addItem:self.view];

        UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.view]];
        [itemBehavior setElasticity:0.2];
        [_animator addBehavior:itemBehavior];

You will need more methods to add velocity according to the direction of the panning, then you will need to find a point where you want to dismiss or hide the childViewController depending in your use case, etc... I did it and it works pretty fine for me as it's using Dynamics it just supports iOS7+

artud2000
  • 544
  • 4
  • 9
  • This doesn't do anything with the navigation bar though. – Doug Smith Apr 03 '14 at 15:04
  • For the navigation bar you have to add a subview with the elements that will fade. then you can do: [UIView animateWithDuration:0.6 animations:^{ _image.alpha = 0.0f; _titleHomeLabel.text = theTitle; _titleHomeLabel.alpha = 1.0f; }]; Make sure you use alpha effect which is animatable instead of hidden – artud2000 Apr 04 '14 at 16:28
  • They do much more than fading, though. They shift position, animate thickness, etc. – Doug Smith Apr 04 '14 at 20:52
  • I see I'll try to reproduce it – artud2000 Apr 05 '14 at 22:18