UIViewPropertyAnimator not stopping on stopAnimation
Asked Answered
C

1

6

I am using UIViewPropertyAnimator to animate interaction with my view (swiping between cards). Everything was working fine, until recently I got following crash in production:

Fatal Exception: NSInternalInconsistencyException

It is an error to release a paused or stopped property animator. Property animators must either finish animating or be explicitly stopped and finished before they can be released.

Since the Crashlytics didn't really provide more context, or I can do is to make sure that all my animators get always finished before the viewController is released.

In one of my attempts I tried to make sure that when I create a new animator, the old one is stopped and finished:

fileprivate var runningAnimator: UIViewPropertyAnimator? {
    didSet {
        if let oldValue = oldValue {
            print(">> before stopping: \(oldValue.isRunning)")
            oldValue.stopAnimation(false)
            print(">> after stopping: \(oldValue.isRunning)")
            oldValue.finishAnimation(at: .end)
            print(">> after finishing: \(oldValue.isRunning)")
        }
    }
}

However, now my app started to crash with this error:

'NSInternalInconsistencyException', reason: 'finishAnimationAtPosition: should only be called on a stopped animator!'

while printing on the output this:

>> before stopping: true
>> after stopping: true

So obviously the oldValue animator is still running even after calling oldValue.stopAnimation(false).

How is this possible?

Cho answered 18/1, 2018 at 13:18 Comment(0)
C
8

Now after more than hour of searching, debugging, analyzing, I was able to find the reason of this happening. I'm sharing this to hopefully save someone else from falling into this trap.

After creating the runningAnimator, I added a completion block that was supposed to set runningAnimator to nil when the animator finished:

runningAnimator!.addCompletion({ [unowned self] (_) in
    self.runningAnimator = nil
})

Now the problem was that the didSet part got called by this completion block. The runningAnimator was already finishing and I guess since the didSet code was happening during the completion block, the oldValue.stopAnimation(false) could not really stop the animator till the completion block haven't finished.

Just for the record, while it might seem that there is infinite recursion in this, this should not happen, because calling stopAnimation and finishAnimation on an already finished animator does not call completion blocks again.

Cho answered 18/1, 2018 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.