91

For some reason, when I try to animate textColor, it won't work. The textColor just suddenly changes from A to B. Is it possible to animate it, for example, red to black?

aheze
  • 24,434
  • 8
  • 68
  • 125
dontWatchMyProfile
  • 45,440
  • 50
  • 177
  • 260

11 Answers11

210

Instead, have you tried using a crossfade transition on the object itself like this, it'll give you a nice fade-in fade-out effect from one color to another:

Objective C

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

Swift 5

UIView.transition(with: creditsLabel, duration: 0.25, options: .transitionCrossDissolve) {
    self.creditsLabel.textColor = .red
}

This is better than using NSTimers, CATextLayers and so on so forth for various reasons. CATextLayer does not have proper support for text kerning or NSAttributedText, and NSTimers are laggy (plus there's too much code). The transition animation above does the trick, and also can be used in a chain animation. I had the same issue and have already tried the solutions above but this simple code works wonders instead.

aheze
  • 24,434
  • 8
  • 68
  • 125
strange
  • 9,654
  • 6
  • 33
  • 47
  • 19
    Great. Works in Swift just the same: `UIView.transitionWithView(creditsLabel, duration: 0.3, options: .TransitionCrossDissolve, animations: { self.creditsLabel.textColor = UIColor.redColor() }, completion: nil)` – SimplGy Aug 20 '15 at 13:40
  • 13
    just a note: This only works when label is static. If you do this and the label's position is being modified, the fade-out animation will show ugly artifact. – Lukas Petr Nov 12 '16 at 14:22
  • Doesn't work for me on Xcode 10.2.1 and Swift 5. 'shokaveli' method below does – Martin May 15 '19 at 08:37
  • I've been looking for a way to do this in SwiftUI, but it does not seem supported at the time. – philipp Sep 25 '19 at 19:10
62

Swift 4 solution:

UIView.transition(with: yourLabel, duration: 0.3, options: .transitionCrossDissolve, animations: {
  self.yourLabel.textColor = .red
}, completion: nil)
budiDino
  • 13,044
  • 8
  • 95
  • 91
  • Note, this only works if you have an instance of `UILabel` for the `with` property. If you do something like `transition(with: outerView.label…)` and try to set it inside, that will fail silently. – Sam Soffes Jul 02 '18 at 20:54
  • Doesn't work for me for some reason. It is suddenly making the change instead of gradual during the animation. :( – ScottyBlades Sep 15 '18 at 21:26
  • 1
    @ScottyBlades maybe you are doing the animation on the background thread? Try wrapping the UIView.transition code inside the DispatchQueue.main.async { } brackets. – budiDino Sep 17 '18 at 10:16
  • 1
    FYI - First time I tried didn't work and that was because i had `options: .easeInOut`, so I changed it to `.transitionCrossDissolve ` and worked. – LeoGalante Feb 04 '20 at 14:23
13

The reason that textColor is not animatable is that UILabel uses a regular CALayer instead of a CATextLayer.

To make textColor animatable (as well as text, font, etc.) we can subclass UILabel to make it use a CATextLayer.

This is quite a lot of work, but luckily I already did it :-)

You can find a complete explanation + a drop-in open source replacement for UILabel in this article

adamsiton
  • 3,642
  • 32
  • 34
9

You could try creating another instance of the UILabel or whatever it is that has the textColor, and then apply the animation between those two instances (the with the old textColor and the one with the new textColor).

benasher44
  • 799
  • 1
  • 6
  • 10
9

This was the ONLY thing that worked for me :

let changeColor = CATransition()
changeColor.duration = 1

CATransaction.begin()

CATransaction.setCompletionBlock {
    selected.label.layer.add(changeColor, forKey: nil)
    selected.label.textColor = .black
}

selected.nameLabel.textColor = .red

CATransaction.commit()
Nikolay Suvandzhiev
  • 8,465
  • 6
  • 41
  • 47
shokaveli
  • 478
  • 7
  • 14
  • Does anyone understand how/why this works...? Or specifically why does the CATransition only seem to work inside the completion block of CATransaction? – The Way Apr 11 '23 at 17:45
6

Here's my code to animate label text color. The important part is to set textColor to clearColor before animation

label.textColor = [UIColor clearColor];
[UIView transitionWithView:label duration:duration/4 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    label.textColor = self.highlightedCellPrimaryTextColor;
} completion:^(BOOL finished) { }];
servalex
  • 175
  • 2
  • 6
6

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.

Jasarien
  • 58,279
  • 31
  • 157
  • 188
  • or: a function that throws out an array with RGB values between A and B and a given percentage. Don't know anything about color algorithms though. – dontWatchMyProfile Mar 11 '10 at 17:42
  • here are good workarounds: [here](http://stackoverflow.com/questions/6442774/is-uilabels-backgroundcolor-not-animatable/6442868#6442868) and [here](http://stackoverflow.com/a/3947389/1076848) thanks to Anna Karenina – jackal Aug 24 '12 at 21:19
  • 4
    You should use `CADisplayLink` rather than `NSTimer` for manually driving animations because it is synchronised with display updates. See [WWDC 2014 Session 236: Building Interruptible and Responsive Interactions](https://developer.apple.com/videos/play/wwdc2014-236/) at about 13:00. – Douglas Hill Jan 08 '16 at 13:22
  • this should NOT be the accepted answer any longer even if it was true at the time. see strange's answer below – bitwit Dec 23 '16 at 22:01
  • 1
    Stack Overflow won't allow me to delete this obsolete answer, as it has been accepted. Checkout @strange's answer below. – Jasarien Jul 08 '17 at 19:05
0

I found it pretty easy placing an UIView below the label and animating its opacity. The label has to be added to that view. Maybe not a good solution from the resources consumption point of view, but pretty straightforward.

catchakos
  • 91
  • 1
  • 6
0

updated swift 3.0

I just changed from @budidino comments

UIView.transition(with: my.label, duration: 0.3, options: .transitionCrossDissolve, animations: { my.label.textColor = .black }, completion: nil)
Shawn Baek
  • 1,928
  • 3
  • 20
  • 34
0

Thanks for @Strange's answer, working perfectly in Swift 5, Xcode 11 with a bit syntax change. I was animating text color for multiple UILabels, if this is also your case, try this

for label in [label1, label2, ...] as [UILabel] { // loop through a @IBOutlet UILabel array
        UIView.transition(with: label, duration: 0.3, options: .transitionCrossDissolve, animations: {
            label.textColor = .label
        }, completion: nil)
}
Daniel Hu
  • 423
  • 4
  • 11
0

Try using core animation

   func textColorBlinking(){
        let changeColor = CATransition()
        changeColor.duration = 1
        changeColor.type = .fade
        changeColor.repeatCount = Float.infinity
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            self.lblHome.layer.add(changeColor, forKey: nil)
            self.lblHome.textColor = .white
        }
        self.lblHome.textColor = .red
        CATransaction.commit()
   }
Yogurt
  • 2,913
  • 2
  • 32
  • 63
Kavya
  • 23
  • 8