3

I built a class to implement a circular transition between view controllers. When I hit the button to navigate to the other view controller a circle starts growing from the button until it fills the screen with the new controller. When I dismiss the view controller I expected this circle to shrink down back to the original position. It's also working. The only problem is that when the dismiss is underway the back of the screen while the circle is shrinking is completely black and after the animation is completed the new viewController appears abruptly.

Here are some photos of the effect:

enter image description here

Here's the code of the custom class:

class customTransition: NSObject, UIViewControllerAnimatedTransitioning{

var duration: TimeInterval = 0.5
var startPoint = CGPoint.zero

var circle = UIView()

var circleColor = UIColor.white
enum transitMode: Int {
    case presenting, dismissing
}

var transitionMode: transitMode = .presenting

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

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    let container = transitionContext.containerView
    guard let to = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
    guard let from = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}

    circleColor = to.backgroundColor ?? UIColor.white

    if transitionMode == .presenting {
        to.translatesAutoresizingMaskIntoConstraints = false
        to.center = startPoint

        circle = UIView()
        circle.backgroundColor = circleColor
        circle.frame = getFrameForCircle(rect: to.frame)
        circle.layer.cornerRadius = circle.frame.width / 2
        circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
        circle.alpha = 0


        circle.addSubview(to)

        to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
        to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
        to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
        to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true

        container.addSubview(circle)

        UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
            self.circle.center = from.center
            self.circle.transform = CGAffineTransform.identity
            self.circle.alpha = 1

        }) { (sucess) in

            transitionContext.completeTransition(sucess)

        }

    } else if transitionMode == .dismissing {


        UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
            self.circle.center = self.startPoint
            self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
            self.circle.alpha = 0

        }) { (sucess) in
            transitionContext.completeTransition(sucess)
        }
    }
}




func getFrameForCircle(rect: CGRect) -> CGRect{
    let width = Float(rect.width)
    let height = Float(rect.height)

    let diameter = CGFloat(sqrtf(width * width + height * height))

    let x: CGFloat = rect.midX - (diameter / 2)
    let y: CGFloat = rect.midY - (diameter / 2)

   return CGRect(x: x, y: y, width: diameter, height: diameter)
}



}

and the implementation...

    let circularTransition = customTransition()

the call for the present view controller... I tried to set secondVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext but when I set this line it ignores completely the animation transition I don't know why... `

 @objc func handlePresent(sender: UIButton){
    let secondVC = nextVC()
    secondVC.transitioningDelegate = self
    present(secondVC, animated: true, completion: nil)

}

delegate methods:

  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    circularTransition.startPoint = presentButton.center
    circularTransition.transitionMode = .presenting
    return circularTransition
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    circularTransition.transitionMode = .dismissing
    circularTransition.startPoint = presentButton.center

    return circularTransition
}

What am I missing here? Any suggestions? No storyboard being used, just code.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Lucas
  • 746
  • 8
  • 23
  • I think you're seeing this problem because you're using `present` and not a [navigationController](https://stackoverflow.com/questions/14233017/difference-between-presentviewcontroller-and-uinavigationcontroller). I recommend trying to use a navigationController to push/pop ViewController and applying your animation when that ViewController is presented. So you would push/pop VC from the NavController without an animation. However, the VC itself would apply your desired animation when appearing either shrinking or growing to/from a circle – DoesData Nov 17 '18 at 22:15
  • You show code that displays the 2nd controller over the 1st. Please also add how you dismiss the 2nd and attempt to transition back to the 1st. – rmaddy Nov 17 '18 at 22:21

1 Answers1

1

If you don't use navigationController, it's necessary to use the .custom mode in the presentedviewController.

 import UIKit

            class TransViewController: UIViewController {

                override func viewDidLoad() {
                    super.viewDidLoad()

                    // Do any additional setup after loading the view.
                }
                 let circularTransition = customTransition()

                @IBOutlet var presentButton : UIButton!

             @IBAction   func handlePresent(sender: UIButton){

                if let secondVC = storyboard?.instantiateViewController(withIdentifier: "next"){
                    secondVC.modalPresentationStyle = .custom
                    secondVC.transitioningDelegate = self
                    present(secondVC, animated: true, completion: nil)
                }
                }

            }


            class BackViewController: UIViewController {

                @IBAction   func dismissMe(sender: UIButton){
                    self.dismiss(animated: true, completion: nil)
                    }

            }

            extension TransViewController: UIViewControllerTransitioningDelegate {

                func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {

                    circularTransition.startPoint = presentButton.center
                    circularTransition.transitionMode = .presenting
                    return circularTransition
                }

                func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
                    circularTransition.transitionMode = .dismissing
                    circularTransition.startPoint = presentButton.center

                    return circularTransition
                }

            }

If there is no from or to view, we have use the from and to view from containView.

   func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    let container = transitionContext.containerView
    var to : UIView!
    var from : UIView!
    to = transitionContext.view(forKey: UITransitionContextViewKey.to)
    if to == nil {to = container}
    from = transitionContext.view(forKey: UITransitionContextViewKey.from)
    if from == nil {from = container}

The rest is same:

 circleColor = to.backgroundColor ?? UIColor.white

    if transitionMode == .presenting {
        to.translatesAutoresizingMaskIntoConstraints = false
        to.center = startPoint

        circle = UIView()
        circle.backgroundColor = circleColor
        circle.frame = getFrameForCircle(rect: to.frame)
        circle.layer.cornerRadius = circle.frame.width / 2
        circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
        circle.alpha = 0


        circle.addSubview(to)

        to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
        to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
        to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
        to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true

        container.addSubview(circle)

        UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
            self.circle.center = from.center
            self.circle.transform = CGAffineTransform.identity
            self.circle.alpha = 1

        }) { (sucess) in

            transitionContext.completeTransition(sucess)

        }

    } else if transitionMode == .dismissing {


        UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
            self.circle.center = self.startPoint
            self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
            self.circle.alpha = 0

        }) { (sucess) in
            transitionContext.completeTransition(sucess)
        }
    }
}  
E.Coms
  • 11,065
  • 2
  • 23
  • 35