Animate text change in UILabel

IosObjective CIpadUilabelCaanimation

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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)

Solution 2 - Ios

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: https://stackoverflow.com/questions/5301305/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.

Solution 3 - Ios

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.

Solution 4 - Ios

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;
                 }];

Solution 5 - Ios

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];

Solution 6 - Ios

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

Solution 7 - Ios

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"

Solution 8 - Ios

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

Solution 9 - Ios

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
})

Solution 10 - Ios

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"

Solution 11 - Ios

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"
}

Solution 12 - Ios

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";
  } );

Solution 13 - Ios

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!"

Solution 14 - Ios

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://stackoverflow.com/a/24318861/1982051:

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)
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoo ParkView Question on Stackoverflow
Solution 1 - IosAnton GaenkoView Answer on Stackoverflow
Solution 2 - IosSwiftArchitectView Answer on Stackoverflow
Solution 3 - IosSwiftArchitectView Answer on Stackoverflow
Solution 4 - IosMapeddView Answer on Stackoverflow
Solution 5 - IosJoo ParkView Answer on Stackoverflow
Solution 6 - IosImanou PetitView Answer on Stackoverflow
Solution 7 - Ioswinnie-ruView Answer on Stackoverflow
Solution 8 - IosMuhammad AsyrafView Answer on Stackoverflow
Solution 9 - IosAlvin GeorgeView Answer on Stackoverflow
Solution 10 - Iosinvisible squirrelView Answer on Stackoverflow
Solution 11 - IosChrisView Answer on Stackoverflow
Solution 12 - IosGary ZView Answer on Stackoverflow
Solution 13 - IosRoman AliyevView Answer on Stackoverflow
Solution 14 - IosChris KlinglerView Answer on Stackoverflow