import UIKit import Shared public protocol SideMenuDismissInteracting: UIViewControllerInteractiveTransitioning { var interactionInProgress: Bool { get } func setup(view: UIView, action: @escaping EmptyClosure) } public final class SideMenuDismissInteractor: UIPercentDrivenInteractiveTransition, SideMenuDismissInteracting { private var action: EmptyClosure? private var shouldFinishTransition = false // MARK: SideMenuDismissInteracting public var interactionInProgress = false public func setup(view: UIView, action: @escaping EmptyClosure) { let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) view.addGestureRecognizer(panRecognizer) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) view.addGestureRecognizer(tapRecognizer) self.action = action } // MARK: Gesture handling @objc private func handleTapGesture(_ recognizer: UITapGestureRecognizer) { action?() } @objc private func handlePanGesture(_ recognizer: UIPanGestureRecognizer) { guard let view = recognizer.view, let containerView = view.superview else { return } let viewWidth = containerView.bounds.size.width guard viewWidth > 0 else { return } let translation = recognizer.translation(in: view) let progress = min(1, max(0, -translation.x / (viewWidth * 0.8))) switch recognizer.state { case .possible, .failed: interactionInProgress = false case .began: interactionInProgress = true shouldFinishTransition = false action?() case .changed: shouldFinishTransition = progress >= 0.5 update(progress) case .cancelled: interactionInProgress = false cancel() case .ended: interactionInProgress = false shouldFinishTransition ? finish() : cancel() @unknown default: interactionInProgress = false cancel() } } }