How to Animate the Textcolor Property of an Uilabel

How to animate the textColor property of an UILabel?

This answer is obsolete, and is not a good solution for the original question. @strange 's answer below is much better and should be used instead of this answer: https://stackoverflow.com/a/20892927/76559

//Old answer below

The textColor property is not specified as being animatable in the docs, so I don't think you can do it with a simple UIView animations block...

This could probably be done pretty crudely with an NSTimer firing every couple of milliseconds, each time setting the colour gradually from one to the other.

I say this is crude because it would require an array or some other container of preset colour values going from the start colour to the finish colour, and I'm sure there's a way you could do this using core animation or something, I just don't know what it is.

Animating UILabel textColor property with UIViewPropertyAnimator

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.

Animate UILabel text color in Swift

it is much easier than working with CALayer

let myLabel: UILabel!

UIView.animateWithDuration(2, animations: { () -> Void in
myLabel.backgroundColor = UIColor.redColor();
})

Thats it...

Edit

Ok, sorry I didn't knew what color you want to change... I have converted your example to swift code...

first

import QuartzCore

than

if let layer: CALayer = self.view.layer as CALayer? {
if let textLayer = CATextLayer() as CATextLayer? {
textLayer.string = "My string"
textLayer.foregroundColor = UIColor.whiteColor().CGColor
textLayer.frame = self.view.bounds
self.view.layer.addSublayer(textLayer)

UIView.animateWithDuration(0.5, animations: { () -> Void in
textLayer.foregroundColor = UIColor.redColor().CGColor
})
}
}

Is it possible to animate UILabel's textcolor change?

Try this

[UIView transitionWithView:myLabel duration:0.25 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
label.textColor = [UIColor redColor];
} completion:^(BOOL finished) {
}];

How to animate text color of a UILabel like a progress bar

To achieve it, follow below steps:

  • Create 2 UILabel (trackLabel and progressLabel).
  • trackLabel have blackColor and progressLabel have redColor.
  • Make progressLabel overlap trackLabel, same leading, top and bottom.
  • Give trackLabel full width with text and progressLabel 0 width.
  • Set progressLabel.lineBreakMode = NSLineBreakByClipping;
  • When you need to animate progress, increase width of progressLabel.

For more detail, you can check my demo project.

enter link description here

How to animate (fade) UILabel textColor in Swift?

UILabel.textColor does not support animation. But you can animate CATextLayer:

Swift:

let textLayer = CATextLayer()
textLayer.string = "Your text"
textLayer.foregroundColor = yourFirstColor
textLayer.frame = yourButton.bounds
yourButton.layer.addSublayer(textLayer)

UIView.animateWithDuration(1) {
textLayer.foregroundColor = yourSecondColor
}

Objective C:

CATextLayer *textLayer = [CATextLayer layer];
[textLayer setString:@"Your text"];
[textLayer setForegroundColor:yourFirstColor];
[textLayer setFrame:yourButton.bounds];
[[yourButton layer] addSublayer:textLayer];

[UIView animateWithDuration:1 animations:^{
textLayer.foregroundColor = yourSecondColor;
}];

Is is possible to animate text color changing only in a part of text in iOS?

You can quite easily achieve this using CoreAnimation possibilities.
I've added a simple demo, you play with it here (just build the project and tap anywhere to see the animation).

The logic is the following:

  1. Create a custom subclass of UIView.
  2. When some text is set, create two similar CATextLayers, each with the same text and frame.
  3. Set different foregroundColor and mask for those layers. The mask of the left layer will be the left part of the view, and the mask of the right layer will be the right part.
  4. Animate foregroundColor for those layers (simultaneously).

The code of a custom view:

class CustomTextLabel: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private var textLayer1: CATextLayer?
private var textLayer2: CATextLayer?

func setText(_ text: String, fontSize: CGFloat) {
// create 2 layers with the same text and size, we'll set the colors for them later
textLayer1 = createTextLayer(text, fontSize: fontSize)
textLayer2 = createTextLayer(text, fontSize: fontSize)

// estimate the frame size needed for the text layer with such text and font size
let textSize = textLayer1!.preferredFrameSize()
let w = frame.width, h = frame.height

// calculate the frame such that both layers will be in center of view
let centeredTextFrame = CGRect(x: (w-textSize.width)/2, y: (h-textSize.height)/2, width: textSize.width, height: textSize.height)
textLayer1!.frame = centeredTextFrame
textLayer2!.frame = centeredTextFrame

// set up default color for the text
textLayer1!.foregroundColor = UIColor.yellow.cgColor
textLayer2!.foregroundColor = UIColor.yellow.cgColor

// set background transparent, that's very important
textLayer1!.backgroundColor = UIColor.clear.cgColor
textLayer2!.backgroundColor = UIColor.clear.cgColor

// set up masks, such that each layer's text is visible only in its part
textLayer1!.mask = createMaskLayer(CGRect(x: 0, y: 0, width: textSize.width/2, height: textSize.height))
textLayer2!.mask = createMaskLayer(CGRect(x: textSize.width/2, y: 0, width: textSize.width/2, height: textSize.height))

layer.addSublayer(textLayer1!)
layer.addSublayer(textLayer2!)
}

private var finishColor1: UIColor = .black, finishColor2: UIColor = .black
func animateText(leftPartColor1: UIColor, leftPartColor2: UIColor, rightPartColor1: UIColor, rightPartColor2: UIColor) {
finishColor1 = leftPartColor2
finishColor2 = rightPartColor2

if let layer1 = textLayer1, let layer2 = textLayer2 {
CATransaction.begin()
let animation1 = CABasicAnimation(keyPath: "foregroundColor")
animation1.fromValue = leftPartColor1.cgColor
animation1.toValue = leftPartColor2.cgColor
animation1.duration = 3.0
layer1.add(animation1, forKey: "animation1")


let animation2 = CABasicAnimation(keyPath: "foregroundColor")
animation2.fromValue = rightPartColor1.cgColor
animation2.toValue = rightPartColor2.cgColor
animation2.duration = 3.0
layer2.add(animation2, forKey: "animation2")

CATransaction.setCompletionBlock {
self.textLayer1?.foregroundColor = self.finishColor1.cgColor
self.textLayer2?.foregroundColor = self.finishColor2.cgColor
}

CATransaction.commit()
}
}

private func createTextLayer(_ text: String, fontSize: CGFloat) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.string = text
textLayer.fontSize = fontSize // TODO: also set font name
textLayer.contentsScale = UIScreen.main.scale

return textLayer
}

private func createMaskLayer(_ holeRect: CGRect) -> CAShapeLayer {
let layer = CAShapeLayer()

let path = CGMutablePath()

path.addRect(holeRect)
path.addRect(bounds)

layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd
layer.opacity = 1

return layer
}
}

The calls of a custom view:

class ViewController: UIViewController {

var customLabel: CustomTextLabel!
override func viewDidLoad() {
super.viewDidLoad()

let viewW = view.frame.width, viewH = view.frame.height
let labelW: CGFloat = 200, labelH: CGFloat = 50

customLabel = CustomTextLabel(frame: CGRect(x: (viewW-labelW)/2, y: (viewH-labelH)/2, width: labelW, height: labelH))
customLabel.setText("Optimizing...", fontSize: 20)
view.addSubview(customLabel)

let tapRecogniner = UITapGestureRecognizer(target: self, action: #selector(onTap))
view.addGestureRecognizer(tapRecogniner)
}

@objc func onTap() {
customLabel.animateText(leftPartColor1: UIColor.blue,
leftPartColor2: UIColor.red,
rightPartColor1: UIColor.white,
rightPartColor2: UIColor.black)
}

}

Swift: UILabel, blink textColor animated

You need to take two colours variables and swap them once animation finished and call the colour changing function recursively.

I answered once here Animate CAGradientLayer in Swift. It looks like same thing.

Although I tried and below is the code which worked for me.

For the convenience I created a custom class of UILabel which can be use easily.

class BlinkLabel: UILabel, CAAnimationDelegate {

var colours: [UIColor] = []
var speed: Double = 1.0
fileprivate var shouldAnimate = true
fileprivate var currentColourIndex = 0

func startBlinking() {
if colours.count <= 1 {
/// Can not blink
return
}

shouldAnimate = true
currentColourIndex = 0
let toColor = self.colours[self.currentColourIndex]
animateToColor(toColor)
}

func stopBlinking() {
shouldAnimate = false
self.layer.removeAllAnimations()
}

fileprivate func animateToColor(_ color: UIColor) {

if !shouldAnimate {return}

let changeColor = CATransition()
changeColor.duration = speed
changeColor.type = .fade
changeColor.repeatCount = 1
changeColor.delegate = self
changeColor.isRemovedOnCompletion = true
CATransaction.begin()
CATransaction.setCompletionBlock {
self.layer.add(changeColor, forKey: nil)
self.textColor = color
}
CATransaction.commit()
}

// MARK:- CAAnimationDelegate
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if flag {

if !self.shouldAnimate {return}

/// Calculating the next colour
self.currentColourIndex += 1
if self.currentColourIndex == self.colours.count {
self.currentColourIndex = 0
}

let toColor = self.colours[self.currentColourIndex]

/// You can remove this delay and directly call the function self.animateToColor(toColor) I just gave this to increase the visible time for each colour.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: {
self.animateToColor(toColor)
})
}
}
}

Usage:

label.colours = [.red, .green, .blue, .orange]
label.speed = 1.0
label.startBlinking()

/// Stop after 10 seconds
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 10) {
self.label.stopBlinking()
}

You can animate label with multiple colours.

Sample Image

How to animate UILabel text size (and color)

As I mentioned, applying a CGAffineTransform to a UILabel to scale it is frustrating for design-oriented developers, for two reasons.

  1. The transform doesn't account for tracking and font variants. San Francisco (Apple's system font) uses two distinct variants of its font depending on the text size.

iOS automatically applies the most appropriate variant based on the point size and the user's accessibility settings. Adjust tracking—the spacing between letters—appropriately.

SF Pro Text is applied to text 19 points or smaller, while SF Pro Display is applied to text 20 points or larger. Each variant has different "tracking" values — the spacing between letters — for each point size (see: the many tracking values under Font Usage and Tracking).

Unfortunately, CGAffineTransform doesn't set a new pointSize, which would otherwise force a redraw of the view. Applying a transform just scales the rasterized bitmap of the UILabel, which means that fonts and tracking values aren't adaptive. So, if we're scaling our label by 2.0 from 10pt to 20pt, our resulting UILabel at 20pt will still be using SF Pro Text with 12pt tracking, instead of SF Pro Display with 19pt tracking. You can see below that the red label is transformed and hence does not adjust its font.


  1. Transforming a view or layer leads to blurriness. As previously alluded to, Core Graphics doesn't re-render the view, but rather transforms the view's 2D map of pixels. The red label below is clearly blurry.

The Solution

Our best bet is to use CATextLayer instead of UILabel, which Apple says is:

A layer that provides simple text layout and rendering of plain or attributed strings.

The CATextLayer docs list var string: Any?, var font: CFTypeRef?, var fontSize: CGFloat, var foregroundColor: CGColor? and more as properties, which most of the time is all we need. Perfect!

All we need to do is add a CAPropertyAnimation — like CABasicAnimation — to the CATextLayer to animate its properties, like so:

// Create the CATextLayer
let textLayer = CATextLayer()
textLayer.string = "yourText"
textLayer.font = UIFont.systemFont(ofSize: startFontSize)
textLayer.fontSize = startFontSize
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.contentsScale = UIScreen.main.scale
textLayer.frame = view.bounds
view.layer.addSublayer(textLayer)

// Animation
let duration: TimeInterval = 10
textLayer.fontSize = endFontSize
let fontSizeAnimation = CABasicAnimation(keyPath: "fontSize")
fontSizeAnimation.fromValue = startFontSize
fontSizeAnimation.toValue = endFontSize
fontSizeAnimation.duration = duration
textLayer.add(fontSizeAnimation, forKey: nil)

and voila! The black text below is our properly scaled CATextLayer. Sharp and with the correct font variant.

Alt Text

h/t to Yinan for the code: https://stackoverflow.com/a/42047777/4848310. Yinan also discusses how to use CATextLayer with Auto Layout constraint-based animations.


Bonus! This also works for animating text color! Just use the foregroundColor property.



Related Topics



Leave a reply



Submit