iosswiftuikit-dynamicscustom-transition

How to make a custom transition behave like a fall under the gravity?


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?"


Solution

  • 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