Swift - UICollisionBehavior delegate methods not being called

579 views Asked by At

I'm doing a simple animation which requires me to handle some collisions with boundaries.

I have a class, viewcontroller, which I extend to be a UICollisionBehaviorDelegate so I can recognize and handle view collisions.

For some reason, when a collision happens, my delegate methods never fire.

class ViewController: UIViewController {

    var fallingImageViews: [UIImageView]!
    var downAnimator: UIDynamicAnimator!

    override func viewDidLoad() {
        super.viewDidLoad()

        //imagine fallingImageViews Initializers happening here

        downAnimator = initializeAnimators()
    }

    func initializeAnimators() -> UIDynamicAnimator {
        let downwardAnimator = UIDynamicAnimator(referenceView: self.view)

        downwardAnimator.addBehavior(setBoundaries())
        downwardAnimator.addBehavior(setGravity())
        downwardAnimator.addBehavior(setBounciness())
        downwardAnimator.delegate = self

        return downwardAnimator
    }

    func setBoundaries() -> UICollisionBehavior {
        let boundaries = UICollisionBehavior(items: fallingImageViews)
        boundaries.collisionDelegate = self

        // prevent collisions between items
        boundaries.collisionMode = .boundaries

        boundaries.setTranslatesReferenceBoundsIntoBoundary = true

        return boundaries
    }
}

// MARK: Collision Behavior Delegate
extension ViewController: UICollisionBehaviorDelegate, UIDynamicAnimatorDelegate {

    func collisionBehavior(_ behavior: UICollisionBehavior, endedContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?) {
        print(identifier)
    }
    func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
        print(identifier)
    }
}
1

There are 1 answers

10
Xavier L. On

I completely scrapped my old answer and updated it. I'm sorry I mislead you, but here is my revised answer:

The idea is that you add behaviors to the objects you want animated (in your case, your fallingImageViews).

So all the code here should actually go into a class that inherits from UIImageView (in my example code you'll see that I'm inheriting from a CardCtrl object, but it might as well be a UIImageView).

The only changes you have to make is your UIDynamicAnimator's reference view has to be superview and that all the the reference views for all your animation behaviors are set to [self].

Here is some example code from one of my old projects:

@IBDesignable class SlidingCard: CardCtrl, UICollisionBehaviorDelegate
{
   ...
    //MARK: - Private Properties

    private var gripHeightAnch: NSLayoutConstraint = NSLayoutConstraint()
    private var animator: UIDynamicAnimator!
    private var dynamicItem: UIDynamicItemBehavior!
    private var collisionBnds: UICollisionBehavior!
    private var snap: UISnapBehavior!
    private var botBndryPt: CGFloat!
    private var gravity: UIGravityBehavior!
    private var pan: UIPanGestureRecognizer!

    ...

    //MARK: - Delegate Methods

    func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem,
                           withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint)
    {
        guard let identifier = identifier else {return}
        if String(describing: identifier) == "bot"
        {
            sendActions(for: .touchDragOutside)
        }
        else
        {
            sendActions(for: .touchDragInside)
        }
    }

    ...

    //MARK: - Private Setup Methods

    private func defineSlideBehavior()
    {
        if animator == nil
        {
            animator = UIDynamicAnimator(referenceView: superview!)
        }
        animator!.removeAllBehaviors()

        //Check to see if behaviors are already installed because .removeAllBehaviors() doesn't
        //always work
        var addItem = true
        var addBounds = true
        var addGravity = true
        for blarHar in animator!.behaviors
        {
            if blarHar.isKind(of: UIDynamicItem.self)
            {
                addItem = false
            }
            if blarHar.isKind(of: UICollisionBehavior.self)
            {
                addBounds = false
            }
            if blarHar.isKind(of: UIGravityBehavior.self)
            {
                addGravity = false
            }
        }
        //Make it so the card doesn't wobble
        if dynamicItem == nil && addItem
        {
            dynamicItem = UIDynamicItemBehavior(items: [self])
            dynamicItem.allowsRotation = false
            dynamicItem.elasticity = 0
        }
        animator!.addBehavior(dynamicItem)

        //Add two boundaries for the drawer to collide with
        if collisionBnds == nil && addBounds
        {
            collisionBnds = UICollisionBehavior(items: [self])
            collisionBnds.collisionDelegate = self
        }
        botBndryPt = frame.maxY * 2 + 1.5
        collisionBnds.removeAllBoundaries()
        collisionBnds.addBoundary(withIdentifier: "top" as NSCopying,
                                  from: CGPoint(x: frame.minX, y: frame.minY - 1.5),
                                  to: CGPoint(x: frame.maxX, y: frame.minY - 1.5))
        collisionBnds.addBoundary(withIdentifier: "bot" as NSCopying,
                                  from: CGPoint(x: frame.minX, y: botBndryPt),
                                  to: CGPoint(x: frame.maxX, y: botBndryPt))
        animator!.addBehavior(collisionBnds)

        //Define the initial gravity that affects the drawer
        if addGravity
        {
            gravity = UIGravityBehavior(items: [self])
            gravity.gravityDirection = CGVector(dx: 0, dy: -gravityStrength)
            animator!.addBehavior(gravity)
        }
    }
}