Search code examples
iosswiftuiviewanimationuiviewanimationtransition

iOS custom transition animation


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


Solution

  • 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