Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 870
Dave G Avatar asked Sep 08 '15 03:09

Dave G


2 Answers

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.

like image 138
pbasdf Avatar answered Sep 24 '22 04:09

pbasdf


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/

like image 34
Shripada Avatar answered Sep 22 '22 04:09

Shripada