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
andprogressLabel
). trackLabel
haveblackColor
andprogressLabel
haveredColor
.- Make
progressLabel
overlaptrackLabel
, same leading, top and bottom. - Give
trackLabel
full width with text andprogressLabel
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.
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:
- Create a custom subclass of UIView.
- When some text is set, create two similar
CATextLayers
, each with the same text and frame. - Set different
foregroundColor
andmask
for those layers. Themask
of the left layer will be the left part of the view, and themask
of the right layer will be the right part. - 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.
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.
- 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.
- 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.
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
How to Resize the Iphone/iPad Simulator
How to Animate the Textcolor Property of an Uilabel
Error: Error Installing Cocoapods: Error: Failed to Build Gem Native Extension
Set the Maximum Character Length of a Uitextfield in Swift
How to Deserialize a JSON String into an Nsdictionary? (For iOS 5+)
Missing Return Uitableviewcell
How to Downgrade or Install an Older Version of Cocoapods
Unexpected Cfbundleexecutable Key
Nsphotolibraryusagedescription Key Must Be Present in Info.Plist to Use Camera Roll
iOS 7: Mpmusicplayercontroller Volume Deprecated. How to Change Device Volume Now
How to Set Back Button Text in Swift
How to Exclude Notes and Reminders Apps from the Uiactivityviewcontroller
How to Get the Name of Image Picked Through Photo Library in Iphone