iosswiftuiviewcontrollerpresentviewcontrolleruiviewanimationtransition

Custom present transition for 2 nested view controllers with 2 simultaneous animations?


I have 2 nested view controllers with different animations. First one has alpha animation and second one moves from bottom to top with constraint.

My current implementation is just show/hide methods inside first view controller in which I run each animation manually with UIView.animate(withDuration:animations:). But it is not convenient for some reasons (for example I need to call custom methods instead of ones provided by iOS).

Is there a way somehow override present/dissmiss transition to allow to call default present/dismiss methods which will run my animations?


Solution

  • Found an appropriate answer here: https://habr.com/ru/articles/424853/

    import UIKit
    
    class AnimatorPresent: NSObject, UIViewControllerAnimatedTransitioning {
        let startFrame: CGRect
    
        init(startFrame: CGRect) {
            self.startFrame = startFrame
        }
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.3
        }
    
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let vcTo = transitionContext.viewController(forKey: .to),
            let snapshot = vcTo.view.snapshotView(afterScreenUpdates: true) else {
                return
            }
    
            let vContainer = transitionContext.containerView
    
            vcTo.view.isHidden = true
            vContainer.addSubview(vcTo.view)
    
            snapshot.frame = self.startFrame
            vContainer.addSubview(snapshot)
    
            UIView.animate(withDuration: 0.3, animations: {
                snapshot.frame = (transitionContext.finalFrame(for: vcTo))
            }, completion: { success in
                vcTo.view.isHidden = false
                snapshot.removeFromSuperview()
                transitionContext.completeTransition(true)
            })
        }
    }
    
    import UIKit
    
    class AnimatorDismiss: NSObject, UIViewControllerAnimatedTransitioning {
    
        let endFrame: CGRect
    
        init(endFrame: CGRect) {
            self.endFrame = endFrame
        }
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.3
        }
    
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            guard let vcTo = transitionContext.viewController(forKey: .to),
            let vcFrom = transitionContext.viewController(forKey: .from),
            let snapshot = vcFrom.view.snapshotView(afterScreenUpdates: true) else {
                return
            }
    
            let vContainer = transitionContext.containerView
            vContainer.addSubview(vcTo.view)
            vContainer.addSubview(snapshot)
    
            vcFrom.view.isHidden = true
    
            UIView.animate(withDuration: 0.3, animations: {
                snapshot.frame = self.endFrame
            }, completion: { success in
                transitionContext.completeTransition(true)
            })
        }
    }
    
    extension VCYellow: UIViewControllerTransitioningDelegate {
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return AnimatorPresent(startFrame: self.startFrame)
        }
    
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return AnimatorDismiss(endFrame: self.startFrame)
        }
    }
    

    In short we create classes implementing UIViewControllerAnimatedTransitioning and place all the animation inside them. Then we add UIViewControllerTransitioningDelegate somewhere and assign its object as transitioningDelegate.