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

基于UICollisionBehavior与UIPanGesture的拼图应用碰撞检测问题

解决UIKit Dynamics在拼图应用中的异常问题

1. 移除重力与弹跳效果

  • 不要向UIDynamicAnimator添加UIGravityBehavior,若已添加则调用animator.removeBehavior(gravityBehavior)移除。
  • 若使用UICollisionBehavior,设置restitution = 0消除弹跳,且按需关闭屏幕边界碰撞:
    let collisionBehavior = UICollisionBehavior(items: puzzlePieces)
    collisionBehavior.restitution = 0
    // 不需要屏幕边界碰撞则注释此行
    // collisionBehavior.translatesReferenceBoundsIntoBoundary = true
    animator.addBehavior(collisionBehavior)
    
  • 避免添加UIBounceBehavior,该行为会主动生成弹跳效果。

2. 禁止拖拽时的旋转

  • 给所有拼图块添加UIDynamicItemBehavior并禁用旋转:
    let itemBehavior = UIDynamicItemBehavior(items: puzzlePieces)
    itemBehavior.allowsRotation = false
    animator.addBehavior(itemBehavior)
    
  • 或让拼图块的自定义视图类实现UIDynamicItem协议,重写属性:
    class PuzzlePieceView: UIView, UIDynamicItem {
        var allowsRotation: Bool { return false }
        // 其他UIDynamicItem协议实现(如需要)
    }
    

3. 修复吸附时的拼图变形问题

直接修改frame后调用updateItem(usingCurrentState:)会与UIKit Dynamics的状态管理冲突,推荐以下两种方案:

  • 方案1:用UISnapBehavior平滑吸附
    // 移除该拼图块的所有现有行为
    animator.behaviors.filter { $0.items.contains(actualPuzzle) }.forEach { animator.removeBehavior($0) }
    // 创建吸附行为,目标为snapshotView的中心
    let snapBehavior = UISnapBehavior(item: actualPuzzle, snapTo: snapshotView.center)
    snapBehavior.damping = 1.0 // 无阻尼,直接到位
    animator.addBehavior(snapBehavior)
    actualPuzzle.isHidden = false
    // 吸附完成后可移除该行为
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        animator.removeBehavior(snapBehavior)
    }
    
  • 方案2:先移除再重新添加到动画器
    animator.removeItem(actualPuzzle)
    actualPuzzle.frame = snapshotView.frame
    actualPuzzle.isHidden = false
    animator.addItem(actualPuzzle)
    // 重新添加之前的动态行为(如itemBehavior、collisionBehavior)
    

4. 基于UIBezierPath检测邻接碰撞,允许重叠且无物理交互

要实现仅检测碰撞、不产生物理阻挡的效果,按以下步骤操作:

  1. 配置拼图块的碰撞路径
    让拼图块的自定义视图返回自定义碰撞路径:
    class PuzzlePieceView: UIView, UIDynamicItem {
        var collisionBoundingPath: UIBezierPath {
            // 返回你定义的四边UIBezierPath
            return yourCustomBezierPath
        }
        var collisionBoundsType: UIDynamicItemCollisionBoundsType {
            return .path
        }
        var allowsRotation: Bool { return false }
    }
    
  2. 配置碰撞行为仅用于检测
    创建UICollisionBehavior并设置代理,禁用物理碰撞响应:
    let collisionBehavior = UICollisionBehavior(items: puzzlePieces)
    collisionBehavior.collisionDelegate = self
    collisionBehavior.collisionMode = .custom
    animator.addBehavior(collisionBehavior)
    
  3. 监听碰撞事件并触发吸附
    实现UICollisionBehaviorDelegate,仅处理碰撞检测、阻止物理交互:
    extension YourViewController: UICollisionBehaviorDelegate {
        func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item1: UIDynamicItem, with item2: UIDynamicItem, at p: CGPoint) {
            guard let piece1 = item1 as? PuzzlePieceView, let piece2 = item2 as? PuzzlePieceView else { return }
            // 根据你的拼图逻辑判断是否为邻接块
            if arePiecesAdjacent(piece1, piece2) {
                // 触发吸附逻辑,比如调用之前的snapBehavior方法
                snapPieceToAdjacentPosition(piece1, target: piece2)
            }
        }
        
        // 阻止物理碰撞的实际作用,仅保留检测事件
        func collisionBehavior(_ behavior: UICollisionBehavior, shouldBeginContactFor item1: UIDynamicItem, with item2: UIDynamicItem, at p: CGPoint) -> Bool {
            return false
        }
    }
    

内容的提问来源于stack exchange,提问作者Piotr Gawłowski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 21:51:21