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

使用UIView.animate时UICollisionBehavior未触发碰撞检测的问题

问题:UICollisionBehavior代理未触发(使用UIView.animate替代UIGravityBehavior时)

已配置UIDynamicBehavior和UICollisionBehavior,但代理方法无法识别任何碰撞。因不想用UIGravityBehavior处理下落动画,改用UIView.animate实现,但碰撞代理方法完全未触发。

相关代码

class Test: UIViewController {
  let collision = UICollisionBehavior()
  let animator: UIDynamicAnimator?

  override func viewDidLoad() {
        /// view logic here
        animator = UIDynamicAnimator(referenceView: view)
        animator?.addBehavior(boundaryCollisionBehavior)
        boundaryCollisionBehavior.collisionDelegate = self
        boundaryCollisionBehavior.collisionMode = .boundaries
        setupViews()
  }

   override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        boundaryCollisionBehavior.addBoundary(
            withIdentifier: "shelf" as NSCopying,
            from: CGPoint(x: 0, y: 200),
            to: CGPoint(x: self.view.frame.width, y: 200)
        )
    }

   func setupViews() {
        let viewsForCollision: [UIView] = [view1, view2, view3]
                UIView.animate(withDuration: animationTime, delay: 0, options: [.curveLinear], animations: {
            viewsForCollision.forEach { $0?.transform = .identity }
        })

       viewsForCollision.compactMap { $0 }.forEach(boundaryCollisionBehavior.addItem)
   }
}

extension Test: UICollisionBehaviorDelegate {
    func collisionBehavior(
        _ behavior: UICollisionBehavior,
        endedContactFor item: UIDynamicItem,
        withBoundaryIdentifier identifier: NSCopying?
    ) {
        guard let boundary = identifier as? String else {
            return
        }

        if boundary == "shelf" {
            // Do something
        }
    }
}

问题原因

UIDynamicAnimator的碰撞检测仅追踪由动力学行为驱动的视图运动,而UIView.animate是基于Core Animation的属性动画,其视图位置变化不会被UIDynamicAnimator监控。即使将视图添加到UICollisionBehavior中,动画过程中视图的位置更新无法被动力学系统感知,因此不会触发碰撞代理方法。

解决方案

必须让UIDynamicAnimator驱动视图运动,才能触发碰撞检测。以下是两种可行方案:

方案1:改用UIDynamics行为实现下落动画

如果需要线性下落效果,可使用UIDynamicItemBehavior设置恒定速度,替代UIView.animate:

class Test: UIViewController {
  let collision = UICollisionBehavior()
  var animator: UIDynamicAnimator? // 修改为可变属性,避免初始化后被释放

  override func viewDidLoad() {
        super.viewDidLoad()
        animator = UIDynamicAnimator(referenceView: view)
        // 修正变量名不一致问题(原代码中boundaryCollisionBehavior应为collision)
        animator?.addBehavior(collision)
        collision.collisionDelegate = self
        collision.collisionMode = .boundaries
        setupViews()
  }

   override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        collision.addBoundary(
            withIdentifier: "shelf" as NSCopying,
            from: CGPoint(x: 0, y: 200),
            to: CGPoint(x: self.view.frame.width, y: 200)
        )
    }

   func setupViews() {
        let viewsForCollision = [view1, view2, view3].compactMap { $0 }
        // 先设置视图初始位置(比如上方)
        viewsForCollision.forEach {
            $0.transform = CGAffineTransform(translationX: 0, y: -300)
            collision.addItem($0)
        }
        
        // 用UIDynamicItemBehavior实现线性下落
        let itemBehavior = UIDynamicItemBehavior(items: viewsForCollision)
        itemBehavior.addLinearVelocity(CGVector(dx: 0, dy: 500), for: viewsForCollision.first!) // 设置初始垂直速度
        itemBehavior.linearDamping = 0 // 无阻尼,保持匀速运动
        animator?.addBehavior(itemBehavior)
   }
}

extension Test: UICollisionBehaviorDelegate {
    func collisionBehavior(
        _ behavior: UICollisionBehavior,
        endedContactFor item: UIDynamicItem,
        withBoundaryIdentifier identifier: NSCopying?
    ) {
        guard let boundary = identifier as? String, boundary == "shelf" else {
            return
        }
        // 碰撞后的逻辑
        print("碰撞到shelf边界")
    }
}

方案2:手动同步UIView.animate的位置到动力学系统(不推荐)

如果必须使用UIView.animate,可在动画闭包中手动更新动力学系统中item的位置,但无法实时触发碰撞检测,仅能在动画结束后检查:

UIView.animate(withDuration: animationTime, delay: 0, options: [.curveLinear], animations: {
    viewsForCollision.forEach { view in
        view?.transform = .identity
        // 手动更新动力学系统中item的位置
        if let item = view as? UIDynamicItem {
            self.collision.updateItem(usingCurrentState: item)
        }
    }
}) { _ in
    // 动画结束后检查是否碰撞
    viewsForCollision.forEach { view in
        guard let item = view as? UIDynamicItem else { return }
        let isColliding = self.collision.items(in: self.view.bounds).contains(item)
        // 处理碰撞逻辑
    }
}

关键注意点

  • 确保UIDynamicAnimator实例被正确初始化并持有(原代码中animator为不可变可选类型,初始化后可能被释放,需改为可变属性)
  • 修正变量名不一致问题:原代码中声明的是collision,但使用时写的是boundaryCollisionBehavior,需统一
  • 碰撞模式设置为.boundaries后,需确保边界已正确添加(viewDidAppear中添加边界是正确时机,此时view的frame已确定)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:07:52