iOS custom transition animation

Stas Ivanov picture Stas Ivanov · May 23, 2017 · Viewed 8.6k times · Source

I have started learning of custom transition animation with using UIViewControllerAnimatedTransitioning protocol. And mostly all videos I've found on youtube are based on the flow when we have new ViewController presented with circle animation or similar to it.

I have problems with implementation my way of transitions. Mostly, what I need, is similar to the facebook app and how they open full-screen image viewer.

So, lets say we have VC1 and VC2. On VC1 we call action to present VC2. And on both VCs we have the same UI element. In my case that is UIImageView. Like you click on imageView on VC1 and it opens detail page for some object with its image at the top. And I want to have animation, that should look like image from VC1 is changing frame to the final frame of image from VC2, and then other content (like labels, buttons, etc) on detail page should appear.

But I've faced with some problems during training.
1. First of all, I don't understand the idea of containerView of transitionContext. But as I see, it is something like a middle-state view between between transitions. Is that correct? But that works strange to me, since even backgroundColor property not working for containerView.
2. I don't understand what exactly I need to animate during transition, and what should be the structure of the containerView subViews. In my example, when presenting VC2, I need, as I understand, to kinda hide all its subViews. Then animate imageView from VC1 to the frame of imageView from VC2, and then make visible all subViews again. So, in this case imageView should be added to containerView? If so, then should it be the actual imageView from VC1, or that is fully new copy of imageView, with the same frame/image, that is just temporarily used during transitions...

It will be helpful to link me to examples/tutorial/code with similar animation

Here is link to how that works in facebook

Answer

dahiya_boy picture dahiya_boy · May 23, 2017

Understanding custom transition animation

Like if you'r navigating from VCA to VCB then

  1. First of all you need to use the UIViewControllerTransitioningDelegate.

The transitioning delegate is responsible for providing the animation controller to be used for the custom transition. The delegate object you designate must conform to the UIViewControllerTransitioningDelegate protocol.

  1. Now you have to use UIViewControllerAnimatedTransitioning

It is responsible for the transition in terms of both the duration and the actual logic of animating the views.

These delegates work like you are in between two VC's and playing with them.

To make the complete transition as successful you have to do below steps:

  1. So for using it first of all you need to

    • set modalPresentationStyle = .custom
    • assign transitonDelegate property.
  2. In func animateTransition(_ : ) you have to use context containerView because you'r in between two VC's so you need any container where you can do any animation, so context provides you that container where you can do animation.

  3. Now you need fromView & toView i.e. VCA.view & VCB.view resp. Now add these two views in containerView and write core logic of animation.

  4. The last important thing to note is the completeTransition(_:) method called on the transition context object. This method must be called once your animation has completed to let the system know that your view controllers have finished transitioning.

This is core fundamental of transition animation.

I don't know FB animation so I just explained rest of your question.

Reference

Any further info you can ask.

Code Addition

On image selection

add in VC_A

var selectedImage: UIImageView?
 let transition = PopAnimator()

  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        coordinator.animate(
          alongsideTransition: {context in
            self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
            self.positionListItems()
          },
          completion: nil
        )
      }
//position all images inside the list
  func positionListItems() {
    let listHeight = listView.frame.height
    let itemHeight: CGFloat = listHeight * 1.33
    let aspectRatio = UIScreen.main.bounds.height / UIScreen.main.bounds.width
    let itemWidth: CGFloat = itemHeight / aspectRatio

    let horizontalPadding: CGFloat = 10.0

    for i in herbs.indices {
      let imageView = listView.viewWithTag(i) as! UIImageView
      imageView.frame = CGRect(
        x: CGFloat(i) * itemWidth + CGFloat(i+1) * horizontalPadding, y: 0.0,
        width: itemWidth, height: itemHeight)
    }

    listView.contentSize = CGSize(
      width: CGFloat(herbs.count) * (itemWidth + horizontalPadding) + horizontalPadding,
      height:  0)
  }

// On image selection
VC_B.transitioningDelegate = self
    present(VC_B, animated: true, completion: nil)



   // add extension
extension VC_A: UIViewControllerTransitioningDelegate {

  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)

    transition.presenting = true
    selectedImage!.isHidden = true

    return transition
  }

  func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    transition.presenting = false
    return transition
  }
}

and animation class

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

  let duration = 1.0
  var presenting = true
  var originFrame = CGRect.zero

  var dismissCompletion: (()->Void)?

  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return duration
  }

  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView

    let toView = transitionContext.view(forKey: .to)!

    let herbView = presenting ? toView : transitionContext.view(forKey: .from)!

    let initialFrame = presenting ? originFrame : herbView.frame
    let finalFrame = presenting ? herbView.frame : originFrame

    let xScaleFactor = presenting ?

      initialFrame.width / finalFrame.width :
      finalFrame.width / initialFrame.width

    let yScaleFactor = presenting ?

      initialFrame.height / finalFrame.height :
      finalFrame.height / initialFrame.height

    let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)

    if presenting {
      herbView.transform = scaleTransform
      herbView.center = CGPoint(
        x: initialFrame.midX,
        y: initialFrame.midY)
      herbView.clipsToBounds = true
    }

    containerView.addSubview(toView)
    containerView.bringSubview(toFront: herbView)

    UIView.animate(withDuration: duration, delay:0.0, usingSpringWithDamping: 0.4,
      initialSpringVelocity: 0.0,
      animations: {
        herbView.transform = self.presenting ?
          CGAffineTransform.identity : scaleTransform
        herbView.center = CGPoint(x: finalFrame.midX,
                                  y: finalFrame.midY)
      },
      completion:{_ in
        if !self.presenting {
          self.dismissCompletion?()
        }
        transitionContext.completeTransition(true)
      }
    )
  }

}

Output :

enter image description here

Git-hub Repo: https://github.com/thedahiyaboy/TDCustomTransitions

  • xcode : 9.2

  • swift : 4