1

I have a UIProgressView which must be be updated while the user holds a button. For a smooth animation I opted to use UIView.animateWithDuration instead of a Timer to update the progress.

What I expect to achieve is: the progress increases while the user holds a button, reaching 100% in 30 seconds (and then stops). If the user releases the button before the time ends, the progress bar just stops in the current progress. If the user then holds the button again, the progress resets to zero and the same process stars all over again.

Problem is I can't cancel the animation once the user releases the button. I tried to use progressView.layer.removeAllAnimation() but didn't work too.

Bellow is my code:

@IBAction func holdValueChanged(_ sender: CustomButton) {        
    if sender.isPressed {
        progressView.progress = 1.0

        UIView.animate(withDuration: 30.0, delay: 0.0, options: UIViewAnimationOptions.curveLinear, animations: { 
            self.progressView.layoutIfNeeded()
            }, completion: { (finished) in
                print("Ended progress animation")
        })
    }
    else {
        // stop progress animation
        progressView.layer.removeAllAnimations()
    }
}
GustavoAzOl
  • 299
  • 2
  • 12
  • By the way, have you checked (with a breakpoint or by logging) that the `removeAllAnimations()` is actually called when you expect it to be called? – Arkku Sep 26 '16 at 14:45
  • Yes, placed a breakpoint and the line is being called. – GustavoAzOl Sep 26 '16 at 14:46

2 Answers2

2

Actually this is because there is no guarantee that the layer, that is being animated is progressView.layer. That's why progressView.layer.removeAllAnimations() will not work. Try this approach:

for (CALayer *layer in self.progressBar.layer.sublayers) {
    [layer removeAllAnimations];
}
mikezs
  • 410
  • 1
  • 8
  • 16
0

Try creating a second (short) animation from the current state to the desired stop point, e.g., something like this (untested):

UIView.animate(withDuration: 0.1, delay: 0,
               options: [ .beginFromCurrentState, .allowUserInteraction ],
               animations: {
    self.progressView.progress = 1.0; // or whatever state it stopped at
}, completion:nil);

Starting a new animation should cancel any existing ones, and .beginFromCurrentState will prevent it from jumping to the end (in case you want to end at some other point). As a bonus, the visual effect of a short animation may also be nicer than just cancelling.

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • Tried this solution but got some strange behavior. The "cancel" animation completes (and even prints a message that I put in the completion block), but the progress bar disappears (as if progress were set to zero) and than starts animating again to the value of the later "cancel" animation and only than stops (and call the completion block of the initial animation). Same thing happens if I only set the progress instead of using a animation block to cancel. – GustavoAzOl Sep 26 '16 at 15:12