I have a button that calls an animateWithDuration code that fades an image out, fades text & a new bg color in , and then resets back to normal. The animation takes a few seconds to complete and works great.
However! There's a problem:
Sometimes this button will be pushed again before the animation finishes. When this happens, I want the current animate to stop and start over again.
Researched Solution Not Working
According to my reading, the solution should be simple, just import QuartzCore and add:
button.layer.removeAllAnimations()
This does remove the animation but the new/second animation is totally messed up. The image that is supposed to be hidden isn't, the text never shows up, and the color transition is all wrong. What's happening!?!
//Animate Finished feedback in footer bar
func animateFinished(textToDisplay: String, footerBtn: UIButton, footerImg: UIImageView) {
//Should cancel any current animation
footerBtn.layer.removeAllAnimations()
footerBtn.alpha = 0
footerBtn.setTitle(textToDisplay, forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Regular", size: 18)
footerBtn.setTitleColor(UIColor(red: 255/255.0, green: 255/255.0, blue: 255/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.backgroundColor = UIColor(red: 217/255.0, green: 217/255.0, blue: 217/255.0, alpha: 1.0)
UIView.animateWithDuration(0.5, delay: 0.0, options: nil, animations: {
footerImg.alpha = 0.01 //Img fades out
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 163/255.0, blue: 00/255.0, alpha: 0.6)
}
, completion: { finished in
UIView.animateWithDuration(0.5, delay: 0.0, options: nil, animations: {
footerBtn.alpha = 1 //Text fades in
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 208/255.0, blue: 11/255.0, alpha: 0.6)
}
, completion: { finished in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: {
footerBtn.alpha = 0.01 //Text fades out
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 173/255.0, blue: 00/255.0, alpha: 0.6)
}
, completion: { finished in
UIView.animateWithDuration(0.5, delay: 0.0, options: nil, animations: {
footerImg.alpha = 1 //Img fades in
}
, completion: { finished in
footerBtn.backgroundColor = UIColor.clearColor()
footerBtn.setTitleColor(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Light", size: 18)
footerBtn.setTitle("", forState: UIControlState.Normal)
footerBtn.alpha = 1
//Completion blocks sets values back to norm
})
})
})
})
}//End of animation
@Shripada suggested I switch to keyframes for more readable code. Keyframe format below. It did not solve the animation interruption problem. If you can solve the problem in nested or keyframe format, please post it!
func animateFinished(textToDisplay: String, footerBtn: UIButton, footerImg: UIImageView) {
//Should cancel any current animation
footerBtn.layer.removeAllAnimations()
footerBtn.alpha = 0
footerBtn.setTitle(textToDisplay, forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Regular", size: 18)
footerBtn.setTitleColor(UIColor(red: 255/255.0, green: 255/255.0, blue: 255/255.0, alpha: 1.0), forState: UIControlState.Normal)
//footerBtn.backgroundColor = UIColor(red: 217/255.0, green: 217/255.0, blue: 217/255.0, alpha: 1.0)
UIView.animateKeyframesWithDuration(3.0 /*Total*/, delay:0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration:0.10, animations:{
footerImg.alpha = 0.01 //Img fades out
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 103/255.0, blue: 00/255.0, alpha: 0.6) //Bg turns to green
})
UIView.addKeyframeWithRelativeStartTime(0.10, relativeDuration:0.30, animations:{
footerBtn.alpha = 1 //Text and green bg fades in
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 173/255.0, blue: 11/255.0, alpha: 0.6) //BG turns greener
})
UIView.addKeyframeWithRelativeStartTime(0.40, relativeDuration:0.50, animations:{
footerBtn.alpha = 0.01 //Text fades out & bg fade out
})
},
completion: { finished in
footerImg.alpha = 1
footerBtn.alpha = 1
footerBtn.backgroundColor = UIColor.clearColor()
footerBtn.setTitleColor(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Light", size: 18)
footerBtn.setTitle("", forState: UIControlState.Normal)
//Completion blocks sets values back to norm
}
)
}//End of 'Finished' animation
I added some print
lines to your animateFinished
method, in order to see what's going on:
func animateFinished(textToDisplay: String, footerBtn: UIButton, footerImg: UIImageView) {
//Should cancel any current animation
print("Remove animations")
footerBtn.layer.removeAllAnimations()
print("Animations removed")
footerBtn.alpha = 0
footerBtn.setTitle(textToDisplay, forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Regular", size: 18)
footerBtn.setTitleColor(UIColor(red: 255/255.0, green: 255/255.0, blue: 255/255.0, alpha: 1.0), forState: UIControlState.Normal)
//footerBtn.backgroundColor = UIColor(red: 217/255.0, green: 217/255.0, blue: 217/255.0, alpha: 1.0)
print("Initial animation setup completed")
UIView.animateKeyframesWithDuration(3.0 /*Total*/, delay:0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration:0.10, animations:{
footerImg.alpha = 0.01 //Img fades out
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 103/255.0, blue: 00/255.0, alpha: 0.6) //Bg turns to green
})
UIView.addKeyframeWithRelativeStartTime(0.10, relativeDuration:0.30, animations:{
footerBtn.alpha = 1 //Text and green bg fades in
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 173/255.0, blue: 11/255.0, alpha: 0.6) //BG turns greener
})
UIView.addKeyframeWithRelativeStartTime(0.40, relativeDuration:0.50, animations:{
footerBtn.alpha = 0.01 //Text fades out & bg fade out
})
},
completion: { finished in
print("Completion block started")
footerImg.alpha = 1
footerBtn.alpha = 1
footerBtn.backgroundColor = UIColor.clearColor()
footerBtn.setTitleColor(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Light", size: 18)
footerBtn.setTitle("", forState: UIControlState.Normal)
//Completion blocks sets values back to norm
print("Completion block finished")
}
)
}//End of 'Finished' animation
If you allow the animations to run to completion, the log shows, as you would expect:
Remove animations
Animations removed
Initial animation setup completed
Completion block started
Completion block finished
But if you tap the button during the animation, you see this:
Remove animations
Animations removed
Initial animation setup completed
Remove animations
Animations removed
Initial animation setup completed
Completion block started
Completion block finished
Completion block started
Completion block finished
What's happening it that the removeAllAnimations
causes the completion block (for the first call) to be executed, after the initial setup for the second call is completed, but before the second animations are undertaken. So, for example, the button title is "" during the second animation.
The fix is relatively straight forward: don't execute the completion block if the animations have not finished:
completion: { finished in
if (!finished) {
return
}
print("Completion block started")
footerImg.alpha = 1
footerBtn.alpha = 1
footerBtn.backgroundColor = UIColor.clearColor()
footerBtn.setTitleColor(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Light", size: 18)
footerBtn.setTitle("", forState: UIControlState.Normal)
print("Completion block finished")
//Completion blocks sets values back to norm
}
Also, as per Shripada, you will need to remove animations from footerImg as well as footerBtn, with:
footerImg.layer.removeAllAnimations()
at the start of the method.
You should avoid sequencing your animations in this kind of nesting using the completion blocks of successive animations. This not only makes it highly unreadable, but also makes it difficult to comprehend and resolve issues like the one you are mentioning.
There is a much better alternative, called key frame animations, and you should consider using it (available iOS 7 onwards).
animateKeyFramesWithDuration:delay:options:animation:completion
Refer documentation
Your animation code can be rewritten using keyframes (PS: I have not test this, just typing it for your ref) -
func animateFinished(textToDisplay: String, footerBtn: UIButton, footerImg: UIImageView) {
//Should cancel any current animation
footerBtn.layer.removeAllAnimations()
footerImg.layer.removeAllAnimations()
footerBtn.alpha = 0
footerBtn.setTitle(textToDisplay, forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Regular", size: 18)
footerBtn.setTitleColor(UIColor(red: 255/255.0, green: 255/255.0, blue: 255/255.0, alpha: 1.0), forState: UIControlState.Normal)
//footerBtn.backgroundColor = UIColor(red: 217/255.0, green: 217/255.0, blue: 217/255.0, alpha: 1.0)
UIView.animateKeyframesWithDuration(3.0 /*Total*/, delay:0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration:0.10, animations:{
footerImg.alpha = 0.01 //Img fades out
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 103/255.0, blue: 00/255.0, alpha: 0.6) //Bg turns to green
})
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration:0.30, animations:{
footerBtn.alpha = 1 //Text and green bg fades in
footerBtn.backgroundColor = UIColor(red: 46/255.0, green: 173/255.0, blue: 11/255.0, alpha: 0.6) //BG turns greener
})
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration:0.50, animations:{
footerBtn.alpha = 0.01 //Text fades out & bg fade out
})
},
completion: { finished in
footerImg.alpha = 1
footerBtn.alpha = 1
footerBtn.backgroundColor = UIColor.clearColor()
footerBtn.setTitleColor(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1.0), forState: UIControlState.Normal)
footerBtn.titleLabel!.font = UIFont(name: "HelveticaNeue-Light", size: 18)
footerBtn.setTitle("", forState: UIControlState.Normal)
//Completion blocks sets values back to norm
}
)
}//End of 'Finished' animation
Also refer this link though it is obj c, very informative. http://www.raizlabs.com/dev/2015/01/uiview-animation-sequencing-and-grouping-techniques/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With