UICollisionBehaviorDelegate methods not called

687 views Asked by At

I worked on an app and implemented collision detection using UIKitDynamics.

Collision detection is working. But somehow the UICollisionBehaviorDelegate methods are not called. No warnings or errors are shown.

I created an example project to demonstrate the issue:

  import UIKit

  class ViewController: UIViewController {
    private var dynamicAnimator: UIDynamicAnimator?

    override func viewDidLoad() {
      super.viewDidLoad()
      // Do any additional setup after loading the view, typically from a nib.
    }

    override func viewDidAppear(_ animated: Bool) {
      super.viewDidAppear(animated)

      self.initPhysics()
    }

    fileprivate var initalized = false
    fileprivate func initPhysics() {
      if initalized {
        return
      }
      initalized = true

      let physicsView = UIView(frame: view.bounds.insetBy(dx: 40, dy: 40))
      physicsView.backgroundColor = UIColor.black

      self.dynamicAnimator = UIDynamicAnimator(referenceView: physicsView)

      physicsView.isUserInteractionEnabled = false
      view.addSubview(physicsView)

      //Create ball
      let frame = CGRect(x: 0, y: 0, width: 40, height: 40)

      let rect = UIView(frame: frame)
      rect.backgroundColor = UIColor.red
      physicsView.addSubview(rect)

      let behavior = UIDynamicItemBehavior(items: [rect])
      behavior.elasticity = 1.0
      behavior.resistance = 0.0
      behavior.friction = 0.0
      behavior.allowsRotation = false
      self.dynamicAnimator?.addBehavior(behavior)

      //collision behavior setup
      let collisionBehavior = UICollisionBehavior(items: [rect])
      collisionBehavior.collisionDelegate = self
      collisionBehavior.setTranslatesReferenceBoundsIntoBoundary(with: .zero)
      collisionBehavior.collisionMode = .everything
      self.dynamicAnimator?.addBehavior(collisionBehavior)

      //Push ball
      let pushBehavior = UIPushBehavior(items: [rect], mode: UIPushBehaviorMode.instantaneous)
      pushBehavior.active = true
      pushBehavior.pushDirection = CGVector(dx: 0.5, dy: 0.5)
      self.dynamicAnimator?.addBehavior(pushBehavior)
    }
  }

  extension ViewController: UICollisionBehaviorDelegate {
    // DELEGATE METHODS NOT CALLED
    func collisionBehavior(_ behavior: UICollisionBehavior,
                           beganContactFor item: UIDynamicItem,
                           withBoundaryIdentifier identifier: NSCopying?,
                           at p: CGPoint) {
      print("began contact boundary")
    }

    func collisionBehavior(_ behavior: UICollisionBehavior,
                           endedContactFor item: UIDynamicItem,
                           withBoundaryIdentifier identifier: NSCopying?) {
      print("contact boundary ended")
    }

    func collisionBehavior(_ behavior: UICollisionBehavior,
                           endedContactFor item1: UIDynamicItem,
                           with item2: UIDynamicItem) {

      print("contact item ended")
    }

    func collisionBehavior(_ behavior: UICollisionBehavior,
                           beganContactFor item1: UIDynamicItem,
                           with item2: UIDynamicItem,
                           at p: CGPoint) {
      print("began contact item")
    }
  }

Base SDK: iOS 11.2, Xcode Version 9.2 (9C40b)

Thank you for your help!

1

There are 1 answers

1
Brandon On BEST ANSWER

That's not how it works (sadly).. You have only ONE item in your collision behaviour and you are asking it to detect collision with what??? If you added a second item, you'll see the delegate get called..

The real question is why? Well, collision detects if the frame of one item intersects with the frame of another.. If it did what you are asking (rect is a child of physicsView), then it will ALWAYS be colliding (which is not what you want)..

Since your physicsView isn't part of the collision behaviour, you get nothing.. You told it to limit the item within its bounds (setTranslatesReferenceBoundsIntoBoundary works on the items added to the behaviour itself), but not to detect collision with the bounds. For example, if the item was outside the physicsView, it wouldn't be able to get in. If it's inside, it can't get out. But it won't be counted as a collision per say (otherwise it is always colliding).

In order to detect collision between your rect and your physicsView, you need to give physicsView some physical properties.. Right now it's just a regular UIView (reference view)..

One other way to work around having to give it physical properties is to add a boundary for it:

collisionBehavior.addBoundary(withIdentifier: "PhysicsViewBoundary" as NSString, for: UIBezierPath(rect: physicsView.bounds))

Now it will call:

func collisionBehavior(_ behavior: UICollisionBehavior,
                       beganContactFor item: UIDynamicItem,
                       withBoundaryIdentifier identifier: NSCopying?,
                       at p: CGPoint) {
    print("began contact boundary")
}

func collisionBehavior(_ behavior: UICollisionBehavior,
                       endedContactFor item: UIDynamicItem,
                       withBoundaryIdentifier identifier: NSCopying?) {
    print("contact boundary ended")
}