Search code examples
iosswiftxcodeuilabeluiviewanimation

Animating UILabel textColor property with UIViewPropertyAnimator


I have a question which seems simple but I found no solution yet.

I animate a view with labels inside using UIViewPropertyAnimator and a slider to manually adjust the animator's fractionComplete property.

Inside the view I animate I have a UILabel and I want to animate its textColor.

I found SO answers suggesting to animate the textColor like this:

UIView.transition(with: yourLabel, duration: 0.3, options: .transitionCrossDissolve, animations: {
  self.yourLabel.textColor = .red
}, completion: nil)

However, for me this does not work, becuase I want the animation to proceed depending on the fractionComplete I set for the animator.


Solution

  • As said in the comments, the textColor property can not be animated. However, there is a technique called color interpolation, which might be a nice workaround.

    You can find multiple ways to solve this in this thread, however, I provide you with one solution also:

    extension UIColor {
    var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
        let components = self.cgColor.components!
    
        switch components.count == 2 {
        case true : 
            return (r: components[0], g: components[0], b: components[0], a: components[1])
        case false: 
            return (r: components[0], g: components[1], b: components[2], a: components[3])
        }
    }
    
    static func interpolate(from fromColor: UIColor, to toColor: UIColor, with progress: CGFloat) -> UIColor {
        let fromComponents = fromColor.components
        let toComponents = toColor.components
        
        let r = (1 - progress) * fromComponents.r + progress * toComponents.r
        let g = (1 - progress) * fromComponents.g + progress * toComponents.g
        let b = (1 - progress) * fromComponents.b + progress * toComponents.b
        let a = (1 - progress) * fromComponents.a + progress * toComponents.a
        
        return UIColor(red: r, green: g, blue: b, alpha: a)
    }
    }
    

    Then, all you have to do is this in the function you call when the slider's value has changed:

    @objc func sliderChanged(_ sender: UISlider) {
         animator.fractionComplete = CGFloat(sender.value)
         yourLabel.textColor = UIColor.interpolate(from: fromColor, to: toColor, with: sender.value)
    }
    

    You are not animating the textColor, rather you change it to a different color with each call to sliderChanged, but the change appears gradual and does not jump from your start color to your second color, so I think it should achieve your desired result.