Josh Schärer
Josh Schärer

Reputation: 153

Swift - the completion ends before the animation does

I currently have the problem that the completion of the animation function ends before the animation itself does.

The array progressBar[] includes multiple UIProgressViews. When one is finished animating I want the next one to start animating and so on. But right now they all start at once.
How can I fix this?

@objc func updateProgress() {

        if self.index < self.images.count {
            progressBar[index].setProgress(0.01, animated: false)
            group.enter()

            DispatchQueue.main.async {
                UIView.animate(withDuration: 5, delay: 0.0, options: .curveLinear, animations: {
                    self.progressBar[self.index].setProgress(1.0, animated: true)
                }, completion: { (finished: Bool) in
                    if finished == true {
                        self.group.leave()
                    }
                })
            }
            group.notify(queue: .main) {
                self.index += 1
                self.updateProgress()
            }
        }
    }

Upvotes: 2

Views: 679

Answers (1)

Rob Napier
Rob Napier

Reputation: 299605

The problem is that UIView.animate() can only be used on animatable properties, and progress is not an animatable property. "Animatable" here means "externally animatable by Core Animation." UIProgressView does its own internal animations, and that conflicts with external animations. This is UIProgressView being a bit over-smart, but we can work around it.

UIProgressView does use Core Animation, and so will fire CATransaction completion blocks. It does not, however, honor the duration of the current CATransaction, which I find confusing since it does honor the duration of the current UIView animation. I'm not actually certain how both of these are true (I would think that the UIView animation duration would be implemented on the transaction), but it seems to be the case.

Given that, the way to do what you're trying looks like this:

func updateProgress() {
    if self.index < self.images.count {
        progressBar[index].setProgress(0.01, animated: false)

        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.index += 1
            self.updateProgress()
        }
        UIView.animate(withDuration: 5, delay: 0, options: .curveLinear,
                       animations: {
                        self.progressBar[self.index].setProgress(1.0, animated: true)
        })
        CATransaction.commit()
    }
}

I'm creating a nested transaction here (with begin/commit) just in case there is some other completion block created during this transaction. That's pretty unlikely, and the code "works" without calling begin/commit, but this way is a little safer than messing with the default transaction.

Upvotes: 4

Related Questions