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

Swift中UIPan拖拽时如何不中断手势将UIView移至其他父视图

Game View

你的手势终止的核心原因是手动调用了removeFromSuperview():当你主动将视图从父视图移除时,系统会默认将该视图上所有正在进行的手势识别状态置为cancelled,导致拖拽直接中断。

下面提供两种可行的实现方案,你可以根据需求选择:


方案1:保留原视图,使用快照实现拖拽(更推荐)

这个方案完全不会修改原视图的层级,不会触发手势中断的问题,也是iOS中绝大多数拖拽交互的标准实现逻辑:

// 你需要新增两个属性来存储临时状态
private var dragSnapshot: UIView?
private var originalItemFrame: CGRect = .zero

@objc func panHandler(gesture: UIPanGestureRecognizer){
    switch (gesture.state) {
    case .began:
        // 1. 保存原视图在顶层视图的坐标,隐藏原视图
        originalItemFrame = convert(bounds, to: controller.view)
        isHidden = true
        // 2. 生成原视图的快照,添加到顶层视图
        dragSnapshot = snapshotView(afterScreenUpdates: true)
        guard let dragSnapshot = dragSnapshot else { return }
        dragSnapshot.frame = originalItemFrame
        controller.view.addSubview(dragSnapshot)
    case .changed:
        let translation = gesture.translation(in: controller.view)
        guard let dragSnapshot = dragSnapshot else { return }
        // 只移动快照视图
        dragSnapshot.center = CGPoint(
            x: dragSnapshot.center.x + translation.x, 
            y: dragSnapshot.center.y + translation.y
        )
        gesture.setTranslation(.zero, in: controller.view)
    case .ended, .cancelled, .failed:
        guard let dragSnapshot = dragSnapshot else { return }
        let dropPoint = dragSnapshot.center
        // 3. 判断落点是否是有效放置区域
        if 你的放置区域判断逻辑(contains: dropPoint) {
            // 放置成功:处理道具放置逻辑,移除原视图
            removeFromSuperview()
            // 可自行扩展放置成功的动画效果
            dragSnapshot.removeFromSuperview()
        } else {
            // 放置失败:快照动画回弹到原位置
            UIView.animate(withDuration: 0.2, animations: {
                dragSnapshot.frame = self.originalItemFrame
            }, completion: { _ in
                // 恢复原视图显示,移除快照
                self.isHidden = false
                dragSnapshot.removeFromSuperview()
            })
        }
        // 清空临时状态
        self.dragSnapshot = nil
    default:
        return
    }
}

这个方案的优势是交互容错性高,就算拖拽中途中断,也不会影响原背包视图的状态,还可以轻松添加回弹、放置动画等效果。


方案2:直接移动原视图(修复现有代码问题)

如果你一定要用原视图直接拖拽,只需要删掉手动调用removeFromSuperview()的代码即可:UIKit规定,当你将一个已有父视图的视图添加到新的父视图时,系统会自动将其从原父视图移除,这个过程不会取消正在进行的手势。你只需要额外处理坐标转换,避免视图位置突然跳转:

@objc func panHandler(gesture: UIPanGestureRecognizer){
    switch (gesture.state) {
    case .began:
        // 将原视图的中心点转换为顶层视图坐标系下的坐标
        let originalCenter = convert(center, to: controller.view)
        // 直接添加到顶层视图,不需要手动调用removeFromSuperview
        controller.view.addSubview(self)
        // 赋值转换后的坐标,避免视图跳变
        self.center = originalCenter
    case .changed:
        let translation = gesture.translation(in: controller.view)
        if let v = gesture.view {
            v.center = CGPoint(
                x: v.center.x + translation.x, 
                y: v.center.y + translation.y
            )
        }
        gesture.setTranslation(.zero, in: controller.view)
    case .ended, .cancelled, .failed:
        // 这里补充放置或者回弹的逻辑:
        // 如果放回背包,就重新添加回背包栏视图,恢复原位置
        // 如果放置到游戏区域,就处理对应的交互逻辑
    default:
        return
    }
}

要注意这个方案需要你自己处理拖拽结束后的视图层级恢复逻辑,如果拖拽失败,要手动将视图加回原背包父视图,否则视图会一直留在顶层。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 20:15:07