如何通过拖拽手势交互式Present UIViewController(修复首次拖拽无效)
解决UIKit交互式Present转场首次拖拽无响应问题
问题背景
在实现UIKit拖拽交互式Present新ViewController时,遇到首次拖拽无任何反应,仅第二次拖拽才能触发交互式转场的问题。初始代码及尝试的转场实现如下:
初始ViewController代码
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // ... 其他初始化代码 let swiper = UIPanGestureRecognizer(target: self, action: #selector(showNext)) swiper.maximumNumberOfTouches = 1 // 修正原代码拼写错误:Touces → Touches view.addGestureRecognizer(swiper) } @objc func showNext(_ sender: UIPanGestureRecognizer) { present(ViewController(), animated: true) } }
尝试的转场扩展代码
extension ViewController: UIViewControllerAnimatedTransitioning { // ... 文档中的标准动画实现代码 } extension ViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { self } func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { self } func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { PercentDrivenInteractiveTransition() } } class PercentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition { override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { // ... 文档中的标准实现代码 } @objc func handleSwipeUpdate(_ sender: UIPanGestureRecognizer) { // ... 文档中的手势处理代码 } }
问题根源
- 手势与交互控制器未关联:原代码中手势直接触发
present,但交互控制器的handleSwipeUpdate方法从未被手势调用,导致首次拖拽时交互逻辑未启动。 - 交互控制器实例生命周期错误:
interactionControllerForPresentation每次返回新的实例,且未与当前手势绑定,首次present时交互控制器无法响应手势事件。 - 缺少交互状态判断:没有标记是否正在进行交互,导致首次present时交互控制器未处于激活状态。
修复方案
1. 持有交互控制器实例
在ViewController中添加交互控制器的属性,用于管理当前的交互状态:
class ViewController: UIViewController { private var interactionController: PercentDrivenInteractiveTransition? // ... 其他代码 }
2. 修改手势触发逻辑
将手势的触发方法改为处理手势状态,并在合适时机初始化交互控制器、触发present:
override func viewDidLoad() { super.viewDidLoad() // ... 其他初始化代码 let swiper = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) swiper.maximumNumberOfTouches = 1 view.addGestureRecognizer(swiper) } @objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) { switch sender.state { case .began: // 初始化交互控制器 interactionController = PercentDrivenInteractiveTransition() // 将手势绑定到交互控制器的处理方法 sender.addTarget(interactionController, action: #selector(PercentDrivenInteractiveTransition.handleSwipeUpdate(_:))) // 触发present,必须设置转场代理 let nextVC = ViewController() nextVC.transitioningDelegate = self present(nextVC, animated: true) case .changed: // 此处可根据需要添加额外的手势状态处理 break case .ended, .cancelled: // 根据拖拽进度决定完成或取消转场 let progress = sender.translation(in: view).x / view.bounds.width if progress > 0.5 { interactionController?.finish() } else { interactionController?.cancel() } // 重置交互控制器 interactionController = nil default: interactionController = nil } }
3. 修正TransitioningDelegate方法
确保仅在交互正在进行时返回交互控制器实例:
extension ViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { self } func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { self } func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { // 只有当交互控制器存在且处于交互状态时返回 return interactionController?.isInteractive ?? false ? interactionController : nil } }
4. 完善交互控制器的手势处理
确保handleSwipeUpdate正确计算并更新转场进度:
class PercentDrivenInteractiveTransition: UIPercentDrivenInteractiveTransition { override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { super.startInteractiveTransition(transitionContext) } @objc func handleSwipeUpdate(_ sender: UIPanGestureRecognizer) { guard let transitionContext = self.transitionContext else { return } let translation = sender.translation(in: transitionContext.containerView) // 以水平向右拖拽为例计算进度,可根据需求调整方向逻辑 let progress = translation.x / transitionContext.containerView.bounds.width // 更新转场进度 update(progress) } }
关键注意点
- 修正原代码中的拼写错误:
maximumNumberOfTouces→maximumNumberOfTouches。 - 必须为被present的ViewController设置
transitioningDelegate = self,否则转场动画和交互逻辑不会生效。 - 交互进度的计算逻辑需根据实际拖拽方向(水平/垂直)进行调整。
内容的提问来源于stack exchange,提问作者Firman Wijaya
相关产品推荐
相关产品推荐

