取消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
相关产品推荐
相关产品推荐

