Search code examples
iosswiftanimationuidynamicanimator

UIDynamicAnimator, removeAllBehaviors() doesn't work at first time


I have UIDynamicAnimator property defined:

lazy fileprivate var animator: UIDynamicAnimator = {
        return UIDynamicAnimator(referenceView: self)
}()

self is subclass from UIView;

In extension of self class, the same file, I have logic with animations, that uses my animator, adds UIDynamicBehavior items:

    let pushBehavior = UIPushBehavior(items: [stampView], mode: .continuous)
//some settings
    let dynamicItemBehavior = UIDynamicItemBehavior(items: [stampView])
//some settings
    let gravityBehavior = UIGravityBehavior(items: [stampView])
//some settings
    let collisionBehavior = UICollisionBehavior(items: [stampView])
//some settings

Everything works fine, but when I try to stop all animations with removeAllBehaviors() animations stop, but behaviors all still in animator.behaviors. The second time I call it, array becomes empty.

//======

For my pushBehavior I add action, which changes var, indicated, that I achieve destination point:

pushBehavior.action = { [unowned stampView] in
            if stampView.center.x <= endPosition.x {
                lastJump = true
            }
        }

In collisionBehavior delegate method I check this variable and try to stop animations with removeAllBehaviors()

public func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
    if lastJump {
        //animator.behaviors.count = 4
        animator.removeAllBehaviors()
        //still, animator.behaviors.count = 4
    }
}

Solution

  • You say you are testing like this:

    public func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
        if lastJump {
            //animator.behaviors.count = 4
            animator.removeAllBehaviors()
            //still, animator.behaviors.count = 4
        }
    }
    

    Well, animator.removeAllBehaviors() is a command that the behaviors should be removed, but that command cannot be obeyed now, because those behaviors are still operating, including the one your code is right in the middle of. If the behaviors really stopped right at that instant, we would never even reach the next line of your code!

    So the animator rightly does not in fact remove the behaviors until after your code has stopped running (also known as the end of the run loop).

    The way to work around this to wait until after your code has stopped before calling removeAllBehaviors(). You can easily do that using my delay utility (https://stackoverflow.com/a/24318861/341994):

    public func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
        if lastJump {
            delay(0.1) {
                animator.removeAllBehaviors()
            }
        }
    }