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

如何通过拖拽手势交互式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) {
    // ... 文档中的手势处理代码
  }
}

问题根源

  1. 手势与交互控制器未关联:原代码中手势直接触发present,但交互控制器的handleSwipeUpdate方法从未被手势调用,导致首次拖拽时交互逻辑未启动。
  2. 交互控制器实例生命周期错误:interactionControllerForPresentation每次返回新的实例,且未与当前手势绑定,首次present时交互控制器无法响应手势事件。
  3. 缺少交互状态判断:没有标记是否正在进行交互,导致首次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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:57:10