Search code examples
iosswiftanimatewithduration

Consecutive Animation Calls Not Working


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

Solution

  • 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.