2

I'm writing an app with a series of cards in a table view, similar to the layout of the Google app for iOS when Google Now cards are enabled. When the user taps on a card, there should be a custom transition to a new view controller which is basically the card looking bigger, almost filling the screen, and it has more details on it. The custom transition itself should look like the card is animated upward and growing in size until it reaches its final size and position, which is now the new view controller holding the card.

I have been trying to approach this using a custom view controller transition. When the card is tapped, I initiate a custom view controller transition with UIModalPresentationCustom, and I set a transition delegate, which itself vends a custom animator and a custom UIPresentationController. In animateTransition:, I add the new view controller's view into the container view, setting the frame to the card's frame initially (so it looks like the card is still there and unchanged). Then I attempt to perform an animation where the presented view's frame grows in size and changes in position so that it moves into its final position.

Here's some of my code which does what I've described above - I'm trying to keep it short and sweet, but I can provide more info if needed:

Transition Delegate

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {

    // NOWAnimationDelegate is my own custom protocol which defines the method for asking the presenting VC for the tapped card's frame.
    UIViewController<NOWAnimationDelegate> *fromVC = (UIViewController<NOWAnimationDelegate> *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *finalVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];

    // Ask the presenting view controller for the frame of the tapped card - this method works.
    toView.frame = [fromVC rectForSelectedCard];

    [transitionContext.containerView addSubview:toView];

    CGRect finalRect = [transitionContext finalFrameForViewController:finalVC];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        toView.frame = finalRect;
    }completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];
}

Custom UIPresentationController

-(CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize {
    return CGSizeMake(0.875*parentSize.width, 0.875*parentSize.height);
}

-(CGRect)frameOfPresentedViewInContainerView {
    CGRect presentedViewFrame = CGRectZero;
    CGRect containerBounds = self.containerView.bounds;

    presentedViewFrame.size = [self sizeForChildContentContainer:(UIView<UIContentContainer> *)self.presentedView withParentContainerSize:containerBounds.size];
    presentedViewFrame.origin.x = (containerBounds.size.width - presentedViewFrame.size.width)/2;
    presentedViewFrame.origin.y = (containerBounds.size.height - presentedViewFrame.size.height)/2 + 10;

    return presentedViewFrame;
}

What I'm finding is happening is that the new view is being automatically set to its final size immediately at the start of the animation, and then the animation is just the new view animating upwards. Using breakpoints, I'm noticing that frameOfPresentedViewInContainerView is called during the [transitionContext.containerView addSubview:toView] call, which would probably explain why this is happening - frameOfPresentedViewInContainerView returns "The frame rectangle to assign to the presented view at the end of the animations" according to the UIPresentationController documentation.

However, I'm not sure how to proceed, or if it's even really possible. All the examples of custom view controller transitions I've seen have all had the final size of the presented view controller static and unchanging during the animation. Is there any way to perform a custom view controller transition with a changing size of the presented view during the animation, or do I have to approach this in a different way?

UberJason
  • 3,063
  • 2
  • 25
  • 50

3 Answers3

1

Basically what you need to do is animating your toView.transform using CGAffineTransform in your Transition Delegate. Steps to do:

  1. Before animating set your toView.frame = your frame when it's not showing
  2. Create, let's say, CGAffineTransformMakeScale to scale from hidden frame to your desired final presented frame
  3. On animation block set toView.transform to the transform that you create on step 2
Aditya Wirayudha
  • 1,024
  • 12
  • 19
  • Thanks for your answer. I haven't worked with CGAffineTransform before, but it sounds like it would scale the frame over time (say, from 50% of its size to full size). Like a little rectangle growing into a big rectangle with the same aspect ratio. But will it work for a situation where I want the width to be unchanged during the transition, but the height does change, thereby revealing content as it grows? That's the sort of transformation I'm looking for. – UberJason Sep 02 '14 at 19:03
  • just set the frame's width before the animation to be equal of the final width and set the scale to 0, maybe? – Aditya Wirayudha Sep 03 '14 at 01:57
  • @AdityaWirayudha Scale has to be 1 in that case because it is a factor that changes the size by multiplication. – Klaas Sep 14 '14 at 18:22
1

As Aditya mentions CGAffineTransform is the way to go here.

Ive got this working now with it maintaining the width:

    CGFloat targetscale=initialHeight/finalHeight;
    CGFloat targetyoffset=(finalHeight-(finalHeight*targetscale))/2;
    int targety=roundf(initialPosition-targetyoffset);

    CGAffineTransform move=CGAffineTransformMakeTranslation(0, targety);
    CGAffineTransform scale=CGAffineTransformMakeScale(1,targetscale);
    toView.transform=CGAffineTransformConcat(scale,move);

This positions the incoming view taking into account the determined scale and then performs both transforms concurrently so the view scales & moves to the final position and size.

You then just set

toView.transform=CGAffineTransformIdentity;

in the animation block and it'll scale to the final location and size.

Note, this only moves and scales in the vertical dimension but can be adapted to scale in all directions like so:

+(CGAffineTransform)transformView:(UIView*)view toTargetRect:(CGRect)rect{

  CGFloat targetscale=rect.size.height/view.frame.size.height;

  CGFloat targetxoffset=(view.frame.size.width-(view.frame.size.width*targetscale))/2;
  int targetx=roundf(rect.origin.x-view.frame.origin.x-targetxoffset);

  CGFloat targetyoffset=(view.frame.size.height-(view.frame.size.height*targetscale))/2;
  int targety=roundf(rect.origin.y-view.frame.origin.y-targetyoffset);

  CGAffineTransform move=CGAffineTransformMakeTranslation(targetx, targety);
  CGAffineTransform scale=CGAffineTransformMakeScale(targetscale,targetscale);

  return CGAffineTransformConcat(scale, move);

}

And don't forget to transform the cell's rect the global coordinate space so the start frame is correct for the whole window not just the cells position in the table.

CGRect cellRect=[_tableView rectForRowAtIndexPath:[_tableView indexPathForSelectedRow]];

cellRect=[self.tableView convertRect:cellRect toView:presenting.view];

hope this helps!

Neilkachu
  • 11
  • 2
0

Apple provides a wwdc sample app 'LookInside', accompanying talk 228: 'A look inside presentation controllers'. It features 2 custom presentations, of which one animates the size of the presented view controller. A look inside that code should help you out ;)

kjellie
  • 352
  • 3
  • 7
  • It might help someone, if it still worked. After fixing some issues in that code which I'm assuming were late changes to a new API, it can't find a plist file. More work than seems reasonable in the *hopes* of something to help. – Chris Prince Sep 21 '14 at 07:05
  • @ChrisPrince - this version compiles: https://developer.apple.com/library/ios/samplecode/LookInside/Introduction/Intro.html – spring Nov 29 '14 at 23:47
  • that apple example project doesnt do what he is after since it's only scaling the vc from the center of the screen – chikuba Dec 17 '15 at 02:04