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

取消UIKit交互式转场后SwiftUI视图点击手势无响应问题

问题描述

我使用带有自定义pop动画和交互式转场的CustomNavigationController,该控制器在标准UIKit视图控制器上运行正常。但当push承载SwiftUI视图的UIHostingController后,若交互式转场被取消,SwiftUI视图会对点击手势失去响应,该问题仅在转场启动后取消时出现。

问题根源与修复方案

问题的核心是转场取消时,UIKit未正确恢复SwiftUI视图的手势识别器层级和交互状态。自定义转场的动画回调中,需要显式处理转场取消的场景,恢复视图交互并强制SwiftUI刷新手势区域。

具体修复步骤:

  • 在CustomPopAnimator的动画完成闭包中,针对转场取消的情况,启用fromView的用户交互,并对UIHostingController触发视图布局更新,同时通过修改rootView的id强制SwiftUI重新生成手势识别器。
  • 优化CustomNavigationController的didShow方法,确保无论转场是否成功完成,只要显示UIHostingController就刷新其视图布局。

修改后的交互式转场代码

class CustomPopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.3
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let fromView = transitionContext.view(forKey: .from),
              let toView = transitionContext.view(forKey: .to) else {
            return
        }

        containerView.insertSubview(toView, belowSubview: fromView)

        let screenWidth = UIScreen.main.bounds.width
        toView.transform = CGAffineTransform(translationX: -screenWidth / 3, y: 0)

        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            fromView.transform = CGAffineTransform(translationX: screenWidth, y: 0)
            toView.transform = .identity
        }) { finished in
            // 重置所有视图的transform状态
            fromView.transform = .identity
            toView.transform = .identity
            
            // 处理转场取消的场景
            if transitionContext.transitionWasCancelled {
                fromView.isUserInteractionEnabled = true
                // 针对UIHostingController强制刷新视图与手势
                if let hostingVC = transitionContext.viewController(forKey: .from) as? UIHostingController<SwiftUIView> {
                    DispatchQueue.main.async {
                        hostingVC.view.setNeedsLayout()
                        hostingVC.view.layoutIfNeeded()
                        // 修改id强制SwiftUI重建视图层级,恢复手势响应
                        hostingVC.rootView.id = UUID().uuidString
                    }
                }
            }
            
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}


class CustomInteractiveTransition: UIPercentDrivenInteractiveTransition {
    var hasStarted = false
    var shouldFinish = false

    override func cancel() {
        super.cancel()
        reset()
    }

    override func finish() {
        super.finish()
        reset()
    }

    private func reset() {
        hasStarted = false
        shouldFinish = false
    }
}


@objc
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
    private let customAnimator = CustomPopAnimator()
    private let customInteractiveTransition = CustomInteractiveTransition()

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        setupGesture()
    }

    private func setupGesture() {
        let edgeSwipeGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleEdgeSwipe(_:)))
        edgeSwipeGesture.edges = .left
        view.addGestureRecognizer(edgeSwipeGesture)
    }

    @objc private func handleEdgeSwipe(_ gesture: UIScreenEdgePanGestureRecognizer) {
        let translation = gesture.translation(in: view)
        let progress = translation.x / view.bounds.width

        switch gesture.state {
        case .began:
            customInteractiveTransition.hasStarted = true
            popViewController(animated: true)
        case .changed:
            customInteractiveTransition.shouldFinish = progress > 0.5
            customInteractiveTransition.update(progress)
        case .ended:
            customInteractiveTransition.hasStarted = false
            customInteractiveTransition.shouldFinish ? customInteractiveTransition.finish() : customInteractiveTransition.cancel()
        case .cancelled:
            customInteractiveTransition.hasStarted = false
            customInteractiveTransition.cancel()
        default:
            break
        }
    }

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

    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return customInteractiveTransition.hasStarted ? customInteractiveTransition : nil
    }

    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        // 只要显示HostingController就刷新布局,确保视图状态正确
        if let hostingController = viewController as? UIHostingController<SwiftUIView> {
            DispatchQueue.main.async {
                hostingController.view.setNeedsLayout()
                hostingController.view.layoutIfNeeded()
            }
        }
    }
}

修改后的SwiftUI代码

import SwiftUI

struct SwiftUIView: View {
    // 新增可修改的id属性,用于强制SwiftUI重建视图
    var id: String = UUID().uuidString
    
    var body: some View {
        VStack {
            Spacer()
            Text("Hello, World!")
                .id(id)
                .background(Color.green)
                .padding()
                .onTapGesture {
                    print("===onclick")
                }
            Spacer()
        }
    }
}

跳转代码(无修改)

let hostingController = UIHostingController(rootView: SwiftUIView())
navigationController?.pushViewController(hostingController, animated: true)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:30:02