You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义全屏侧滑返回取消后SwiftUI视图交互失效如何解决?

问题根因

这个问题是自定义交互转场和UIHostingController的视图事件传递机制不兼容导致的:

  • 自定义pop转场取消时,UIKit的转场上下文未正确将视图响应权交还给SwiftUI层,事件传递链被阻断
  • 重复添加的侧滑手势和SwiftUI内置手势存在响应冲突
  • 自定义转场动画器缺少取消场景的状态清理逻辑
最优解决方案:复用系统侧滑手势扩展全屏响应

不需要自己实现整套交互转场逻辑,直接复用系统自带的interactivePopGestureRecognizer,扩展其响应范围到全屏,天然兼容SwiftUI,不会出现取消后无响应的问题:

class BaseNavigationController: UINavigationController, UIGestureRecognizerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupFullScreenPopGesture()
    }
    
    private func setupFullScreenPopGesture() {
        // 获取系统侧滑手势的内部target
        guard let interactivePopGesture = interactivePopGestureRecognizer,
              let targets = interactivePopGesture.value(forKey: "targets") as? [NSObject],
              let target = targets.first?.value(forKey: "target") else { return }
        // 复用系统侧滑的action
        let action = Selector(("handleNavigationTransition:"))
        
        // 创建全屏pan手势,使用和系统侧滑相同的target和action
        let fullScreenPan = UIPanGestureRecognizer(target: target, action: action)
        fullScreenPan.delegate = self
        view.addGestureRecognizer(fullScreenPan)
        
        // 禁用原系统边缘侧滑手势
        interactivePopGesture.isEnabled = false
    }
    
    // 手势触发条件判断
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        // 根视图控制器不触发侧滑
        guard viewControllers.count > 1 else { return false }
        // 只响应右滑
        if let pan = gestureRecognizer as? UIPanGestureRecognizer {
            let translation = pan.translation(in: view)
            return translation.x > 0
        }
        return true
    }
}
自定义转场场景的修复方案

如果一定要保留自定义的转场动画,按以下步骤修改原有代码即可修复问题:

  1. 避免重复添加手势,给视图控制器增加已添加手势标记
// 关联对象key
private var popGestureAddedKey: UInt8 = 0
private func addPanGesture(_ viewController: UIViewController) {
    if objc_getAssociatedObject(viewController, &popGestureAddedKey) as? Bool == true {
        return
    }
    let popRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecognizer(recognizer:)))
    popRecognizer.delegate = self
    viewController.view.addGestureRecognizer(popRecognizer)
    objc_setAssociatedObject(viewController, &popGestureAddedKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
  1. 交互转场取消时强制修复UIHostingController的响应状态,修改handlePanRecognizer的结束分支:
@objc func handlePanRecognizer(recognizer: UIPanGestureRecognizer){
    var progress = recognizer.translation(in: self.view).x / self.view.bounds.size.width
    progress = min(1, max(0, progress))
    if recognizer.state == .began {
        self.interactivePopTransition = UIPercentDrivenInteractiveTransition()
        self.popViewController(animated: true)
    }else if recognizer.state == .changed {
        interactivePopTransition.update(progress)
    }else if recognizer.state == .ended || recognizer.state == .cancelled {
        if progress > 0.2 {
            interactivePopTransition.finish()
        }else{
            interactivePopTransition.cancel()
            // 转场取消后修复SwiftUI响应状态
            DispatchQueue.main.async { [weak self] in
                guard let self = self, let topVC = self.topViewController else { return }
                topVC.view.isUserInteractionEnabled = false
                topVC.view.isUserInteractionEnabled = true
                topVC.view.superview?.bringSubviewToFront(topVC.view)
            }
        }
        interactivePopTransition = nil
    }
}
  1. 完善自定义NavigationPopTransition的收尾逻辑,在动画结束后正确调用completeTransition:
class NavigationPopTransition: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromVC = transitionContext.viewController(forKey: .from),
              let toVC = transitionContext.viewController(forKey: .to),
              let containerView = transitionContext.containerView as? UIView else {
            return
        }
        // 添加toVC到容器
        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        toVC.view.frame = CGRect(x: -containerView.bounds.width * 0.3, y: 0, width: containerView.bounds.width, height: containerView.bounds.height)
        
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            fromVC.view.frame = CGRect(x: containerView.bounds.width, y: 0, width: containerView.bounds.width, height: containerView.bounds.height)
            toVC.view.frame = containerView.bounds
        }) { finished in
            // 必须调用completeTransition通知系统转场结束
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

内容的提问来源于stack exchange,提问作者hemo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 17:15:03