iosswiftuiviewanimationtransition

iOS Swift: Custom interactive transition


I wrote a custom swipe transition that works fine on a modal presentation. But in a push presentation the "to" view position is not animating.

I've tried the same code with switching the translation with alpha and it works.

The from view works perfectly, it's just the to view that stays fixed during the animation.

func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    let duration = transitionDuration(using: transitionContext)
    let container = transitionContext.containerView

    let toController = transitionContext.viewController(forKey: .to)
    toController?.beginAppearanceTransition(true, animated: true)

    guard
    let to = transitionContext.view(forKey: .to),
    let from = transitionContext.view(forKey: .from)
        else {
        print("To or from view are nil!")
        fatalError()
    }

    container.addSubview(to)

    let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
    var toStartingPoint: CGPoint
    var fromEndingPoint: CGPoint

    switch self.from {
    case .down:
        toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
    case .top:
        toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
        fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
    case .right:
        toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
    case .left:
        toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
        fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
    }

    to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)

    animator.addAnimations({
        from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
    }, delayFactor: 0.0)

    animator.addAnimations({
        to.transform = .identity
    }, delayFactor: 0.0)


    animator.addCompletion { [weak self] position in
        switch position {
        case .start:
            self?.auxCancelCompletion?()
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        case .end:
            self?.auxEndCompletion?()
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            from.transform = .identity
            to.transform = .identity
        default:
            transitionContext.completeTransition(false)
            self?.auxAnimationsCancel?()
        }
    }

    if let auxAnimations = auxAnimations {
        animator.addAnimations(auxAnimations)
    }

    self.animator = animator
    self.context = transitionContext

    animator.addCompletion { [unowned self] _ in
        self.animator = nil
        self.context = nil
    }
    animator.isUserInteractionEnabled = true

    return animator
}

I was thinking that was a problem about delegates but the navigationDelgate is correctly set, otherwise I think I wouldn't see any animation..

Delegate setting:

override func viewDidLoad() {
    super.viewDidLoad()
    transitionHelper = SwipeInteractiveTransitionHelper(withDelegate: self)
}


extension TodayViewController: UINavigationControllerDelegate {

    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return transitionHelper?.swipeTransition
    }

    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return transitionHelper?.swipeTransition
    }
}

and here is the custom push coordinator, where the viewController is the next view controller, and where I attach the delegate.

    case .pushCustom:
        guard let navigationController = currentViewController.navigationController else {
            fatalError("Can't push a view controller without a current navigation controller")
        }
        guard let current = currentViewController as? UINavigationControllerDelegate else {
            fatalError("Can't push a view controller without a current  navigation delegate")
        }
        navigationController.delegate = current
        navigationController.pushViewController(viewController, animated: true) { [weak self] in
            self?.currentViewController = SceneCoordinator.actualViewController(for: viewController)
            completion?()
        }

Solution

  • Solved animating a snapshot of the destination view, instead of directly animating the destination view.

    let to = transitionContext.view(forKey: .to) let toViewSnapshot = to.snapshotView(afterScreenUpdates: true)

    Just use the toViewSnapshot for the animation