swiftuianimation

UI Animation using animateKeyframes does nothing to Button Title Color


I'm trying to animate the color of my UIButton Title Color using animateKeyframes but it does nothing except load the last state in the animation, in this case purpleText. I'm using the same code to animate button colors and a UIView background color without any issues.

I checked also changed the button to a custom button in Interface Builder but that didn't help either.

        UIView.animateKeyframes(withDuration: 12, delay: 12, options: [.allowUserInteraction, .repeat, .autoreverse], animations: {
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25, animations: {
                self.infoButton.setTitleColor(blueText, for: UIControlState.normal)
            })
            UIView.addKeyframe(withRelativeStartTime: 0.2, relativeDuration: 0.25, animations: {
                self.infoButton.setTitleColor(greenText, for: UIControlState.normal)
            })
            UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.25, animations: {
                self.infoButton.setTitleColor(yellowText, for: UIControlState.normal)
            })
            UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.25, animations: {
                self.infoButton.setTitleColor(redText, for: UIControlState.normal)
            })
            UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 0.25, animations: {
                self.infoButton.setTitleColor(purpleText, for: UIControlState.normal)
            })
        }, completion: nil)

Solution

  • Calling setTitleColor() inside the animation block doesn't work because UIView.animate only works on animatable properties, like infoButton.backgroundColor.

    Unfortunately, button properties like tintColor aren't animatable. You also shouldn't directly access the button's titleLabel, which is nil for system buttons.

    Instead, you can use UIView.transition with setTitleColor(). You can then put it inside a Timer... I usually don't like to use timers for animations, but I can't think of any other way.

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let colors: [UIColor] = [.blue, .green, .yellow, .red, .purple]
        var currentIndex = 0
        
        _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
            if colors.indices.contains(currentIndex) {
                UIView.transition(with: self.infoButton, duration: 0.5, options: [.transitionCrossDissolve, .allowUserInteraction]) {
                    self.infoButton.setTitleColor(colors[currentIndex], for: .normal)
                }
                currentIndex += 1
            } else {
                currentIndex = 0
            }
        }
    }
    

    Result:

    Button title color animates and cycles