Animate text change in UILabel
Asked Answered
I

14

158

I'm setting a new text value to a UILabel. Currently, the new text appears just fine. However, I'd like to add some animation when the new text appears. I'm wondering what I can do to animate the appearance of the new text.

Involve answered 18/6, 2010 at 22:44 Comment(1)
For Swift 5, see my answer that shows 2 different ways to solve your problem.Pronominal
I
17

Here is the code to make this work.

[UIView beginAnimations:@"animateText" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:1.0f];
[self.lbl setAlpha:0];
[self.lbl setText:@"New Text";
[self.lbl setAlpha:1];
[UIView commitAnimations];
Involve answered 18/6, 2010 at 23:28 Comment(2)
This is not a fade. This is a fade out, disappear entirely, then fade in. It's basically a slow motion flicker, but it is still a flicker.Anetta
please don't name your UILabels lblBoden
L
213

I wonder if it works, and it works perfectly!

Objective-C

[UIView transitionWithView:self.label 
                  duration:0.25f 
                   options:UIViewAnimationOptionTransitionCrossDissolve 
                animations:^{

    self.label.text = rand() % 2 ? @"Nice nice!" : @"Well done!";

  } completion:nil];

Swift 3, 4, 5

UIView.transition(with: label,
              duration: 0.25,
               options: .transitionCrossDissolve,
            animations: { [weak self] in
                self?.label.text = (arc4random()() % 2 == 0) ? "One" : "Two"
         }, completion: nil)
Lifeless answered 17/9, 2014 at 12:43 Comment(3)
Note that the TransitionCrossDissolve is the key to this working.Cabretta
You don't need [weak self] in UIView animation blocks. See https://mcmap.net/q/152544/-is-it-necessary-to-use-unowned-self-in-closures-of-uiview-animatewithdurationArchean
Note that you have to use the transition API as noted in the sample code, not the animate API, which has almost the same signature.Octogenarian
A
167

Objective-C

To achieve a true cross-dissolve transition (old label fading out while new label fading in), you don't want fade to invisible. It would result in unwanted flicker even if text is unchanged.

Use this approach instead:

CATransition *animation = [CATransition animation];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionFade;
animation.duration = 0.75;
[aLabel.layer addAnimation:animation forKey:@"kCATransitionFade"];

// This will fade:
aLabel.text = "New"

Also see: Animate UILabel text between two numbers?

Demonstration in iOS 10, 9, 8:

Blank, then 1 to 5 fade transition


Tested with Xcode 8.2.1 & 7.1, ObjectiveC on iOS 10 to 8.0.

► To download the full project, search for SO-3073520 in Swift Recipes.

Anetta answered 3/5, 2013 at 20:55 Comment(7)
When I set two labels at the same time it gives flicker on the simulator though.Intermingle
Possibly misused? The point of my approach is to not use 2 labels. You use a single label, -addAnimation:forKey to that label, then change the label's text.Anetta
@ConfusedVorlon, please verify your statement. and check against the posted project at swiftarchitect.com/recipes/#SO-3073520.Anetta
I may have got the wrong end of the stick on this in the past. I thought that you could define the transition once for the layer, then make subsequent changes and get a 'free' fade. It seems that you have to specify the fade at each change. So - provided you call the transition each time, this works fine in my test on iOS9.Fefeal
Please remove the misleading but doesn't seem to work in iOS9 if you feel it is no longer appropriate. Thank you.Anetta
OMG! This is awesome! I had no idea you could do this! Testing now in my app :DTowne
How can I have the same effect to happen simultaneously on more views (more labels) once you click a button that changes 2-3 text label for example? @AnettaPow
A
150

Swift 4

The proper way to fade a UILabel (or any UIView for that matter) is to use a Core Animation Transition. This will not flicker, nor will it fade to black if the content is unchanged.

A portable and clean solution is to use a Extension in Swift (invoke prior changing visible elements)

// Usage: insert view.fadeTransition right before changing content


extension UIView {
    func fadeTransition(_ duration:CFTimeInterval) {
        let animation = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name:
            CAMediaTimingFunctionName.easeInEaseOut)
        animation.type = CATransitionType.fade
        animation.duration = duration
        layer.add(animation, forKey: CATransitionType.fade.rawValue)
    }
}

Invocation looks like this:

// This will fade
aLabel.fadeTransition(0.4)
aLabel.text = "text"

Blank, then 1 to 5 fade transition


► Find this solution on GitHub and additional details on Swift Recipes.

Anetta answered 25/12, 2014 at 8:26 Comment(7)
hello I used your code in my project, thought you might be interested: github.com/goktugyil/CozyLoadingActivitySuffering
Nice - If you could use the MIT license (lawyer-talk version of that same license), it could be used in commercial apps...Anetta
I don't really understand about the license stuff. What prevents people from using it in commercial apps now?Suffering
Many companies cannot use standard licenses unless listed on opensource.org/licenses and some will only incorporate Cocoapods adhering to opensource.org/licenses/MIT. That MIT license guarantees your Cocoapod can be used freely by everyone and anyone.Anetta
The issue with the current WTF License is that it does not cover your grounds: for starters, it does not claim you as the author (copyright), and as such, proves not that you can give privileges away. In the MIT License, you first claim ownership, which you then use to relinquish rights. You should really read up on MIT if you want professionals to leverage your code, and participate to your open source effort.Anetta
Yes but I am guessing if "professional" someone had an interest in my repo, I guess I would have received an email or an issue. The few people who are using this are amused at the license and I think for now it is a bigger win to put a smile on someones face, than to make it more "professional".Suffering
It helped me. I wanna to know that how to run this as background because if i have a list of say 100 values and i wanna to repeat this list for infinite time and simultaneously i can access or touch another button on screen. in short ui cannot freezGroin
T
24

since iOS4 it can be obviously done with blocks:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Tucky answered 28/6, 2012 at 19:36 Comment(1)
Not a cross-fade transition.Anetta
I
17

Here is the code to make this work.

[UIView beginAnimations:@"animateText" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:1.0f];
[self.lbl setAlpha:0];
[self.lbl setText:@"New Text";
[self.lbl setAlpha:1];
[UIView commitAnimations];
Involve answered 18/6, 2010 at 23:28 Comment(2)
This is not a fade. This is a fade out, disappear entirely, then fade in. It's basically a slow motion flicker, but it is still a flicker.Anetta
please don't name your UILabels lblBoden
P
6

With Swift 5, you can choose one of the two following Playground code samples in order to animate your UILabel's text changes with some cross dissolve animation.


#1. Using UIView's transition(with:duration:options:animations:completion:) class method

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Car"

        view.backgroundColor = .white
        view.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggle(_:)))
        view.addGestureRecognizer(tapGesture)
    }

    @objc func toggle(_ sender: UITapGestureRecognizer) {
        let animation = {
            self.label.text = self.label.text == "Car" ? "Plane" : "Car"
        }
        UIView.transition(with: label, duration: 2, options: .transitionCrossDissolve, animations: animation, completion: nil)
    }

}

let controller = ViewController()
PlaygroundPage.current.liveView = controller

#2. Using CATransition and CALayer's add(_:forKey:) method

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let label = UILabel()
    let animation = CATransition()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Car"

        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        // animation.type = CATransitionType.fade // default is fade
        animation.duration = 2

        view.backgroundColor = .white
        view.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggle(_:)))
        view.addGestureRecognizer(tapGesture)
    }

    @objc func toggle(_ sender: UITapGestureRecognizer) {
        label.layer.add(animation, forKey: nil) // The special key kCATransition is automatically used for transition animations
        label.text = label.text == "Car" ? "Plane" : "Car"
    }

}

let controller = ViewController()
PlaygroundPage.current.liveView = controller
Pronominal answered 11/12, 2016 at 23:42 Comment(1)
Both animations works like crossDissolve only. Anyways thank you for such a descriptive answer.Sowell
U
6

Swift 4.2 version of SwiftArchitect's solution above (works great):

    // Usage: insert view.fadeTransition right before changing content    

extension UIView {

        func fadeTransition(_ duration:CFTimeInterval) {
            let animation = CATransition()
            animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
            animation.type = CATransitionType.fade
            animation.duration = duration
            layer.add(animation, forKey: CATransitionType.fade.rawValue)
        }
    }

Invocation:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"
Utham answered 26/9, 2018 at 10:50 Comment(0)
K
5

UILabel Extension Solution

extension UILabel{

  func animation(typing value:String,duration: Double){
    let characters = value.map { $0 }
    var index = 0
    Timer.scheduledTimer(withTimeInterval: duration, repeats: true, block: { [weak self] timer in
        if index < value.count {
            let char = characters[index]
            self?.text! += "\(char)"
            index += 1
        } else {
            timer.invalidate()
        }
    })
  }


  func textWithAnimation(text:String,duration:CFTimeInterval){
    fadeTransition(duration)
    self.text = text
  }

  //followed from @Chris and @winnie-ru
  func fadeTransition(_ duration:CFTimeInterval) {
    let animation = CATransition()
    animation.timingFunction = CAMediaTimingFunction(name:
        CAMediaTimingFunctionName.easeInEaseOut)
    animation.type = CATransitionType.fade
    animation.duration = duration
    layer.add(animation, forKey: CATransitionType.fade.rawValue)
  }

}

Simply Called function by

uiLabel.textWithAnimation(text: "text you want to replace", duration: 0.2)

Thanks for all the tips guys. Hope this will help in long term

Kulseth answered 21/2, 2019 at 17:8 Comment(0)
S
4

Swift 2.0:

UIView.transitionWithView(self.view, duration: 1.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
    self.sampleLabel.text = "Animation Fade1"
    }, completion: { (finished: Bool) -> () in
        self.sampleLabel.text = "Animation Fade - 34"
})

OR

UIView.animateWithDuration(0.2, animations: {
    self.sampleLabel.alpha = 1
}, completion: {
    (value: Bool) in
    self.sampleLabel.alpha = 0.2
})
Stinko answered 11/2, 2016 at 9:0 Comment(3)
The first one works, but the second just calls completion right away, regardless the duration I pass. Another issue is that I can't press any button while I'm animating with the first solution.Hamza
To allow interactions while animating, I added one option, options: [.TransitionCrossDissolve, .AllowUserInteraction]Hamza
In the first option using self.view makes the whole view change, this means that only TransitionCrossDissolve can be used. Instead it is better to transition only the text: "UIView.transitionWithView(self.sampleLabel, duration: 1.0...". Also in my case it worked better with "..., completion: nil)".Breadfruit
I
4

The animation's duration and timingFunction properties can be omitted, in which case they will take their default values of 0.25 and .curveEaseInEaseOut, respectively.

let animation = CATransition()
label.layer.add(animation, forKey: nil)
label.text = "New text"

is the same as writing this:

let animation = CATransition()
animation.duration = 0.25
animation.timingFunction = .curveEaseInEaseOut
label.layer.add(animation, forKey: nil)
label.text = "New text"
Intimate answered 7/4, 2019 at 15:17 Comment(0)
D
3

Swift 4.2 solution (taking 4.0 answer and updating for new enums to compile)

extension UIView {
    func fadeTransition(_ duration:CFTimeInterval) {
        let animation = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name:
            CAMediaTimingFunctionName.easeInEaseOut)
        animation.type = CATransitionType.fade
        animation.duration = duration
        layer.add(animation, forKey: CATransitionType.fade.rawValue)
    }
}

func updateLabel() {
myLabel.fadeTransition(0.4)
myLabel.text = "Hello World"
}
Duwe answered 25/1, 2019 at 15:1 Comment(0)
S
3

There is one more solution to achieve this. It was described here. The idea is subclassing UILabel and overriding action(for:forKey:) function in the following way:

class LabelWithAnimatedText: UILabel {
    override var text: String? {
        didSet {
            self.layer.setValue(self.text, forKey: "text")
        }
    }

    override func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == "text" {
            if let action = self.action(for: layer, forKey: "backgroundColor") as? CAAnimation {
                let transition = CATransition()
                transition.type = kCATransitionFade

                //CAMediatiming attributes
                transition.beginTime = action.beginTime
                transition.duration = action.duration
                transition.speed = action.speed
                transition.timeOffset = action.timeOffset
                transition.repeatCount = action.repeatCount
                transition.repeatDuration = action.repeatDuration
                transition.autoreverses = action.autoreverses
                transition.fillMode = action.fillMode

                //CAAnimation attributes
                transition.timingFunction = action.timingFunction
                transition.delegate = action.delegate

                return transition
            }
        }
        return super.action(for: layer, forKey: event)
    }
}

Usage examples:

// do not forget to set the "Custom Class" IB-property to "LabelWithAnimatedText"
// @IBOutlet weak var myLabel: LabelWithAnimatedText!
// ...

UIView.animate(withDuration: 0.5) {
    myLabel.text = "I am animated!"
}
myLabel.text = "I am not animated!"
Selfexplanatory answered 28/1, 2019 at 12:25 Comment(1)
This is the best answer. Transitions sometimes mess up with other animations, especially if you also want to animate other animatable properties of the label, like the text color.Projectile
M
1

This is a C# UIView extension method that's based on @SwiftArchitect's code. When auto layout is involved and controls need to move depending on the label's text, this calling code uses the Superview of the label as the transition view instead of the label itself. I added a lambda expression for the action to make it more encapsulated.

public static void FadeTransition( this UIView AView, double ADuration, Action AAction )
{
  CATransition transition = new CATransition();

  transition.Duration = ADuration;
  transition.TimingFunction = CAMediaTimingFunction.FromName( CAMediaTimingFunction.Linear );
  transition.Type = CATransition.TransitionFade;

  AView.Layer.AddAnimation( transition, transition.Type );
  AAction();
}

Calling code:

  labelSuperview.FadeTransition( 0.5d, () =>
  {
    if ( condition )
      label.Text = "Value 1";
    else
      label.Text = "Value 2";
  } );
Montsaintmichel answered 9/8, 2016 at 4:58 Comment(0)
T
0

If you would like to do this in Swift with a delay try this:

delay(1.0) {
        UIView.transitionWithView(self.introLabel, duration: 0.25, options: [.TransitionCrossDissolve], animations: {
            self.yourLabel.text = "2"
            }, completion:  { finished in

                self.delay(1.0) {
                    UIView.transitionWithView(self.introLabel, duration: 0.25, options: [.TransitionCrossDissolve], animations: {
                        self.yourLabel.text = "1"
                        }, completion:  { finished in

                    })
                }

        })
    }

using the following function created by @matt - https://mcmap.net/q/64374/-dispatch_after-gcd-in-swift:

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

which will become this in Swift 3

func delay(_ delay:Double, closure:()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.after(when: when, execute: closure)
}
Taber answered 29/6, 2016 at 17:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.