2

The effect could be implemented like the following code in animateTransition method:

UIView.animateWithDuration(duration, 
  delay: 0, 
  usingSpringWithDamping: 0.3, 
  initialSpringVelocity: 0.0, 
  options: .CurveLinear, 
  animations: {
    fromVC.view.alpha = 0.5
    toVC.view.frame = finalFrame
  }, 
  completion: {_ -> () in
    fromVC.view.alpha = 1.0
    transitionContext.completeTransition(true)
  })

But how could I implement it using gravity and collision behaviors(UIGravityBehavior, UICollisionBehavior)?

And a more general question may be "How to use the UIDynamicAnimator to customize the transitions between UIViewControllers?"

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
mrahmiao
  • 1,291
  • 1
  • 10
  • 22

1 Answers1

4

You can find the solution under the post Custom view controller transitions with UIDynamic behaviors by dasdom.

And the Swift code:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {
  return 1.0
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {

  // 1. Prepare for the required components
  let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
  let finalFrame = transitionContext.finalFrameForViewController(toVC)
  let containerView = transitionContext.containerView()
  let screenBounds = UIScreen.mainScreen().bounds

  // 2. Make toVC at the top of the screen
  toVC.view.frame = CGRectOffset(finalFrame, 0, -1.0 * CGRectGetHeight(screenBounds))
  containerView.addSubview(toVC.view)

  // 3. Set the dynamic animators used by the view controller presentation
  var animator: UIDynamicAnimator? = UIDynamicAnimator(referenceView: containerView)
  let gravity = UIGravityBehavior(items: [toVC.view])
  gravity.magnitude = 10

  let collision = UICollisionBehavior(items: [toVC.view])
  collision.addBoundaryWithIdentifier("GravityBoundary",
    fromPoint: CGPoint(x: 0, y: screenBounds.height),
    toPoint: CGPoint(x: screenBounds.width, y: screenBounds.height))

  let animatorItem = UIDynamicItemBehavior(items: [toVC.view])
  animatorItem.elasticity = 0.5

  animator!.addBehavior(gravity)
  animator!.addBehavior(collision)
  animator!.addBehavior(animatorItem)

  // 4. Complete the transition after the time of the duration
  let nsecs = transitionDuration(transitionContext) * Double(NSEC_PER_SEC)
  let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(nsecs))

  dispatch_after(delay, dispatch_get_main_queue()) {
    animator = nil
    transitionContext.completeTransition(true)
  }
}

A little more complicated than using animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion: method.

EDIT: Fixed a bug when 'transitionDuration' is ≤1

Aviel Gross
  • 9,770
  • 3
  • 52
  • 62
Noah Blues
  • 1,339
  • 2
  • 11
  • 19
  • One problem with this approach is in step 4. The transitionDuration doesn't affect the duration of the animation when using dynamics, so in effect, the time when completeTransition is called is not related to when the animation actually stops. You can use the delegate method, dynamicAnimatorDidPause:, to determine when the animator's items come to rest, and call completeTransition in there. – rdelmar Jul 14 '14 at 04:29
  • @rdelmar The duration time that `transitionDuration:` returns may be a little more longer that the animation actually needs otherwise the animation would be abnormal. And `dynamicAnimatorDidPause:` method do not have a transition context related to its parameter, how do you call `completeTransition:` in it? – Noah Blues Jul 14 '14 at 22:38
  • 1
    On your first point, my point was that the time you put there is essentially a hard coded number (which isn't good) that you have to figure out empirically based on the strength of the gravity, the distance of the fall, the elasticity, etc. As to your second point, you create a property for the transitionContext which you assign in animateTransition. – rdelmar Jul 15 '14 at 00:05
  • @rdelmar Hard coded number, you mean the duration time **transitionDuration** returns? I always regarded the return value as a upper bound of the transition. And I've made the the custom transition animator class conformed to **UIDynamicAnimatorDelegate** and assigned it to the delegate of **animator**, which is an instance of **UIDynamicAnimator**. Since the existence of **elasticity** of **UIDynamicItemBehavior**, the method **dynamicAnimatorDidPause:** haven't been called at all. – Noah Blues Jul 15 '14 at 13:00