How to disable UINavigationBar Animation when the UIPercentDrivenInteractiveTransition is cancelled

246 Views Asked by At

I have implemented a custom interactive transition for detecting the middle screen swipe with the UIPanGestureRecognizer along with UIPercentDrivenInteractiveTransition and animating it with UIViewControllerAnimatedTransitioning.

For now, I have a problem when the UIPercentDrivenInteractiveTransition was cancelled but the UINavigationBar pop animation was still animated like this

Example

Is there anyway to disable or cancel the animation when there are some situation like this?

Here is my code https://github.com/kanottonp/NavBarBugPOC

ViewController.swift

@IBOutlet weak var button: UIButton!
static var count = 1

private var percentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition!
private var panGestureRecognizer: UIPanGestureRecognizer!

override func viewDidLoad() {
    super.viewDidLoad()
    addGesture()
    self.navigationController?.setNavigationBarHidden(false, animated: true)
    self.title = "Hello \(ViewController.count)"
    ViewController.count += 1
}

@IBAction func onTouch(_ sender: Any) {
    guard let newVC = storyboard?.instantiateViewController(withIdentifier: "ViewController") else {
        return
    }
    self.navigationController?.pushViewController(newVC, animated: true)
}

private func addGesture() {
    guard panGestureRecognizer == nil else {
        return
    }
    guard self.navigationController?.viewControllers.count > 1 else {
        return
    }
    panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture(_:)))
    panGestureRecognizer.cancelsTouchesInView = true;
    panGestureRecognizer.delaysTouchesBegan = true;
    panGestureRecognizer.maximumNumberOfTouches = 1
    self.view.addGestureRecognizer(panGestureRecognizer)
    self.navigationController?.interactivePopGestureRecognizer?.delegate = panGestureRecognizer as? UIGestureRecognizerDelegate
    
}

@objc private func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
    let percent = max(panGesture.translation(in: view).x, 0) / view.frame.width
    switch panGesture.state {
        
    case .began:
        navigationController?.delegate = self
        if panGesture.velocity(in: view).x > 0 {
            _ = navigationController?.popViewController(animated: true)
        }
    case .changed:
        if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
            percentDrivenInteractiveTransition.update(percent)
        }
        
    case .ended:
        let velocity = panGesture.velocity(in: view).x
        // Continue if drag more than 50% of screen width or velocity is higher than 300
        if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
            if percent > 0.5 || velocity > 300 {
                percentDrivenInteractiveTransition.finish()
            } else {
                percentDrivenInteractiveTransition.cancel()
            }
        }
        
    case .cancelled, .failed:
        percentDrivenInteractiveTransition.cancel()
        
    default:
        break
    }
}

extension ViewController: UINavigationControllerDelegate

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

public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    
    navigationController.delegate = nil
    
    if panGestureRecognizer.state == .began && panGestureRecognizer.velocity(in: view).x > 0 {
        percentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()
        percentDrivenInteractiveTransition.completionCurve = .easeInOut
    } else {
        percentDrivenInteractiveTransition = nil
    }
    
    return percentDrivenInteractiveTransition
}

SlideAnimatedTransitioning.swift

extension SlideAnimatedTransitioning: UIViewControllerAnimatedTransitioning

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    // use animator to implement animateTransition
    
    let animator = interruptibleAnimator(using: transitionContext)
    animator.startAnimation()
}

func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    
    if let propertyAnimator = propertyAnimator {
        return propertyAnimator
    }
    
    let containerView = transitionContext.containerView
    let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
    let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
    
    let fromView = transitionContext.view(forKey: .from)!
    let toView = transitionContext.view(forKey: .to)!
    
    
    toView.frame = transitionContext.finalFrame(for: toViewController)
    toView.frame = CGRect(x: toView.frame.origin.x, y: toView.frame.origin.y, width: toView.frame.size.width, height: toView.frame.size.height + toView.frame.origin.y)

    let width = containerView.frame.width
    
    var offsetLeft = fromView.frame
    offsetLeft.origin.x = width
    
    var offscreenRight = toView.frame
    offscreenRight.origin.x = -width / 3.33;
    
    toView.frame = offscreenRight;
    
    toView.layer.opacity = 0.9
    
    containerView.insertSubview(toView, belowSubview: fromView)

    let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: UICubicTimingParameters(animationCurve: .easeInOut))
    
    animator.addAnimations {
        toView.frame = CGRect(x: fromView.frame.origin.x, y: toView.frame.origin.y, width: toView.frame.width, height: toView.frame.height)
        fromView.frame = offsetLeft
        toView.layer.opacity = 1.0

    }
    
    animator.addCompletion { (success) in
        toView.layer.opacity = 1.0
        fromView.layer.opacity = 1.0
        fromViewController.navigationItem.titleView?.layer.opacity = 1

        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        
        self.propertyAnimator = nil

    }
    
    self.propertyAnimator = animator
    return animator
}

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    if transitionContext?.transitionWasCancelled == true { return 0 }
    return 2
}
0

There are 0 best solutions below