0

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.

chnski
  • 557
  • 1
  • 4
  • 20
  • Label text color is not an animatable view property. – matt Jan 10 '21 at 12:59
  • @matt I want to animate it anyway, it looks wrong that the rest of the view animates as it supposed to and the text color just changes abruptly. Is there any work around perhaps? – chnski Jan 10 '21 at 13:01
  • There are multiple ways you can go about doing this. [This thread](https://stackoverflow.com/questions/19781597/ios-smooth-color-change-transition-animation?rq=1) seems like a good starting point – Bartosz Kunat Jan 10 '21 at 13:41

1 Answers1

2

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.

lajosdeme
  • 2,189
  • 1
  • 11
  • 20