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

你的手势终止的核心原因是手动调用了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
相关产品推荐
相关产品推荐

