Letter by letter animation for UILabel?
Asked Answered
D

15

32

Is there a way to animate the text displayed by UILabel. I want it to show the text value character by character.

Help me with this folks

Durning answered 27/7, 2012 at 11:13 Comment(2)
I don't understand your question. You mean if the label's value is being set from string1 to string2, you want the characters of string2 to pop in—letter by letter (using an animation)Lubalubba
FYI this repository might be helpful for some people. Also this question is similarDantzler
B
67

Update for 2018, Swift 4.1:

extension UILabel {

    func animate(newText: String, characterDelay: TimeInterval) {

        DispatchQueue.main.async {

            self.text = ""

            for (index, character) in newText.enumerated() {
                DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
                    self.text?.append(character)
                }
            }
        }
    }

}

calling it is simple and thread safe:

myLabel.animate(newText: myLabel.text ?? "May the source be with you", characterDelay: 0.3)

@objC, 2012:

Try this prototype function:

- (void)animateLabelShowText:(NSString*)newText characterDelay:(NSTimeInterval)delay
{    
    [self.myLabel setText:@""];

    for (int i=0; i<newText.length; i++)
    {
        dispatch_async(dispatch_get_main_queue(),
        ^{
            [self.myLabel setText:[NSString stringWithFormat:@"%@%C", self.myLabel.text, [newText characterAtIndex:i]]];
        });

        [NSThread sleepForTimeInterval:delay];
    }
}

and call it in this fashion:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
    [self animateLabelShowText:@"Hello Vignesh Kumar!" characterDelay:0.5];
});
Betterment answered 27/7, 2012 at 12:12 Comment(3)
The problem with this approach is if you begin writing a word at the end of one line, and it then needs to shift to a second line (i.e. if you add enough characters). This will cause half of the word to appear on the first line, then the word to jerk down to the next line once it realises.Lemons
Correct me if i'm not right, this approach uses CPU for animation. But as far as i know this one is not recommended way. Is anyone know Core Animation way(Which basically use GPU for animation)?Aragon
Thanks for this great answer. I'm curious how to avoid garbled text when using this async effect in a tableview that reloads often? I have tried using NSLock () and a separate DispatchQueue for the text animations but I still get multiple strings asynchronously mixing into garbled text. Thanks for any help you can provide.Velodrome
R
9

Here's @Andrei G.'s answer as a Swift extension:

extension UILabel {

    func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.25) {
        text = ""
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
            for character in typedText.characters {
                dispatch_async(dispatch_get_main_queue()) {
                    self.text = self.text! + String(character)
                }
                NSThread.sleepForTimeInterval(characterInterval)
            }
        }
    }

}
Rosary answered 19/6, 2015 at 23:27 Comment(1)
I want springDampning effect that is available in UIButton. How can we acheive this in UILabel?Microanalysis
T
7

This might be better.

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *string =@"Risa Kasumi & Yuma Asami";

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:string forKey:@"string"];
    [dict setObject:@0 forKey:@"currentCount"];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(typingLabel:) userInfo:dict repeats:YES];
    [timer fire];


}

-(void)typingLabel:(NSTimer*)theTimer
{
    NSString *theString = [theTimer.userInfo objectForKey:@"string"];
    int currentCount = [[theTimer.userInfo objectForKey:@"currentCount"] intValue];
    currentCount ++;
    NSLog(@"%@", [theString substringToIndex:currentCount]);

    [theTimer.userInfo setObject:[NSNumber numberWithInt:currentCount] forKey:@"currentCount"];

     if (currentCount > theString.length-1) {
        [theTimer invalidate];
    }

    [self.label setText:[theString substringToIndex:currentCount]];
}
Towandatoward answered 20/2, 2013 at 18:29 Comment(0)
M
4

I have write a demo , you can use it , it support ios 3.2 and above

in your .m file

- (void)displayLabelText
{

    i--;
    if(i<0)
    {
        [timer invalidate];
    }
    else
    {
        [label setText:[NSString stringWithFormat:@"%@",[text substringToIndex:(text.length-i-1)]]];
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 60)];
    [label setBackgroundColor:[UIColor redColor]];
    text = @"12345678";
    [label setText:text];
    [self.view addSubview:label];
    i=label.text.length;
    timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(displayLabelText) userInfo:nil repeats:YES];
    [timer fire];    
}

in your .h file

@interface labeltextTestViewController : UIViewController {
    UILabel *label;
    NSTimer *timer;
    NSInteger i;
    NSString *text;
}

with the demo , i think you can do in your situation , with a little change the code look like very very ugly because i have to go to have dinner, you can majorization it.

Mindszenty answered 27/7, 2012 at 12:22 Comment(0)
H
4

Swift 3 ,Still credit on Andrei G. concept.

extension UILabel{

func setTextWithTypeAnimation(typedText: String, characterInterval: TimeInterval = 0.25) {
    text = ""
    DispatchQueue.global(qos: .userInteractive).async {

        for character in typedText.characters {
            DispatchQueue.main.async {
                self.text = self.text! + String(character)
            }
            Thread.sleep(forTimeInterval: characterInterval)
        }

    }
}

}
Humbug answered 29/11, 2016 at 10:52 Comment(0)
T
3

I have written a lightweight library specifically for this use case called CLTypingLabel, available on GitHub.

It is efficient, safe and does not sleep any thread. It also provide pause and continue interface. Call it anytime you want and it won't break.

After installing CocoaPods, add the following like to your Podfile to use it:

pod 'CLTypingLabel'

Sample Code

Change the class of a label from UILabel to CLTypingLabel; enter image description here

@IBOutlet weak var myTypeWriterLabel: CLTypingLabel!

At runtime, set text of the label will trigger animation automatically:

myTypeWriterLabel.text = "This is a demo of typing label animation..."

You can customize time interval between each character:

myTypeWriterLabel.charInterval = 0.08 //optional, default is 0.1

You can pause the typing animation at any time:

myTypeWriterLabel.pauseTyping() //this will pause the typing animation
myTypeWriterLabel.continueTyping() //this will continue paused typing animation

Also there is a sample project that comes with cocoapods

Tame answered 21/2, 2016 at 22:28 Comment(0)
L
2

Update: 2019, swift 5

It works! Just copy paste my answer & see your result

Also create an @IBOutlet weak var titleLabel: UILabel! before the viewDidLoad()

override func viewDidLoad() {
    super.viewDidLoad()

    titleLabel.text = ""
    let titleText = "⚡️Please Vote my answer"
    var charIndex = 0.0
    for letter in titleText {
        Timer.scheduledTimer(withTimeInterval: 0.1 * charIndex, repeats: false) { (timer) in
            self.titleLabel.text?.append(letter)
        }
         charIndex += 1
    }

   }
Lindgren answered 24/12, 2019 at 4:26 Comment(0)
T
2

SwiftUI + Combine example:

struct TypingText: View {
    typealias ConnectablePublisher = Publishers.Autoconnect<Timer.TimerPublisher>
    private let text: String
    private let timer: ConnectablePublisher
    private let alignment: Alignment

    @State private var visibleChars: Int = 0

    var body: some View {
        ZStack(alignment: self.alignment) {
            Text(self.text).hidden() // fixes the alignment in position
            Text(String(self.text.dropLast(text.count - visibleChars))).onReceive(timer) { _ in
                if self.visibleChars < self.text.count {
                    self.visibleChars += 1
                }
            }
        }
    }

    init(text: String) {
        self.init(text: text, typeInterval: 0.05, alignment: .leading)
    }

    init(text: String, typeInterval: TimeInterval, alignment: Alignment) {
        self.text = text
        self.alignment = alignment
        self.timer = Timer.TimerPublisher(interval: typeInterval, runLoop: .main, mode: .common).autoconnect()
    }
}
Trichology answered 31/12, 2019 at 14:59 Comment(0)
L
1

There is no default behaviour in UILabel to do this, you could make your own where you add each letter one at a time, based on a timer

Locarno answered 27/7, 2012 at 11:36 Comment(0)
D
1

I wrote this based on the first answer:

import Foundation

var stopAnimation = false

extension UILabel {

    func letterAnimation(newText: NSString?, completion: (finished : Bool) -> Void) {
        self.text = ""
        if !stopAnimation {
            dispatch_async(dispatch_queue_create("backroundQ", nil)) {
                if var text = newText {
                    text = (text as String) + " "

                    for(var i = 0; i < text.length;i++){
                        if stopAnimation {
                            break
                        }

                        dispatch_async(dispatch_get_main_queue()) {
                            let range = NSMakeRange(0,i)
                            self.text = text.substringWithRange(range)
                        }

                        NSThread.sleepForTimeInterval(0.05)
                    }
                    completion(finished: true)
                }
            }
            self.text = newText as? String
        }
    }
}
Distraint answered 19/10, 2015 at 9:2 Comment(0)
P
1

I know it is too late for the answer but just in case for someone who is looking for the typing animation in UITextView too. I wrote a small library Github for Swift 4. You can set the callback when animation is finished.

@IBOutlet weak var textview:TypingLetterUITextView!
textview.typeText(message, typingSpeedPerChar: 0.1, completeCallback:{
       // complete action after finished typing }

Also, I have UILabel extension for typing animation.

label.typeText(message, typingSpeedPerChar: 0.1, didResetContent = true, completeCallback:{
       // complete action after finished typing }
Possible answered 24/11, 2017 at 1:15 Comment(0)
J
0

Based on @Adam Waite answer. If someone would like to use this with a completion closure.

    func setTextWithTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.05, completion: () ->()) {
    text = ""
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
        for character in typedText.characters {
            dispatch_async(dispatch_get_main_queue()) {
                self.text = self.text! + String(character)
            }
            NSThread.sleepForTimeInterval(characterInterval)
        }

        dispatch_async(dispatch_get_main_queue()) {
            completion()
        }
    }
}
Jazmin answered 6/2, 2016 at 16:13 Comment(0)
D
0

Modifying @Adam Waite's code (nice job, btw) for displaying the text word by word:

    func setTextWithWordTypeAnimation(typedText: String, characterInterval: NSTimeInterval = 0.25) {
    text = ""
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
        let wordArray = typedText.componentsSeparatedByString(" ")
        for word in wordArray {
            dispatch_async(dispatch_get_main_queue()) {
                self.text = self.text! + word + " "
            }
            NSThread.sleepForTimeInterval(characterInterval)
        }
    }
Dine answered 14/4, 2016 at 16:25 Comment(0)
N
0

A little improvement to the answer provided by Andrei, not to block the main thread.

- (void)animateLabelShowText:(NSString*)newText characterDelay:(NSTimeInterval)delay
{
  [super setText:@""];

  NSTimeInterval appliedDelay = delay;

  for (int i=0; i<newText.length; i++)
  {    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, appliedDelay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [super setText:[NSString stringWithFormat:@"%@%c", self.text, [newText characterAtIndex:i]]];
    });

    appliedDelay += delay;

}
Newmark answered 19/7, 2016 at 13:8 Comment(0)
K
0

I wrote a small open source lib to do it. I built it with NSAttributedString such that the label won't resize during the animation. It also supports typing sounds and curser animation

Check out here:

https://github.com/ansonyao/AYTypeWriter

Kyongkyoto answered 5/10, 2018 at 6:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.