Animate Text Change of UILabel
Asked Answered
B

3

24

I want to animate text changes in a UILabel.

For example: old text moves up and new text moves in from the bottom.

I already realised that I would need to labels. One for the old and one for the new text. The second UILabel is located below the first. Then both animate up.

However, how do you cut off the first label as it animates past the top portion of the frame?

Bueno answered 10/11, 2015 at 14:26 Comment(2)
Please post any attempt where you tried to solve this yourself.Talesman
By proposing I can think of a way by having 2 labels in your question, you lead the answer towards that direction. There is, however, a much shorter and elegant approach leveraging Core Animation.Speos
S
120

Use aUIView Extension

Animate in place using a single label:
- Leverage upon the built-in CALayer animations, and a UIView extension
- In the Storyboard, place the UILabel inside a UIView with Clip Subviews flag.
- pushTransition is applicable to most UIView

1. Extension

// Usage: insert view.pushTransition right before changing content
extension UIView {
    func pushTransition(_ duration:CFTimeInterval) {
        let animation:CATransition = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name:
            kCAMediaTimingFunctionEaseInEaseOut)
        animation.type = kCATransitionPush
        animation.subtype = kCATransitionFromTop
        animation.duration = duration
        layer.add(animation, forKey: kCATransitionPush)
    }
}

2. Invocation

if let aLabel = label {
    aLabel.pushTransition(0.4) // Invoke before changing content
    aLabel.text = "\(count)"
    count += 1
}

3. Example

Animation with a kCAMediaTimingFunctionEaseInEaseOut curve, a 0.4 second duration, and a kCATransitionFromTop direction .

CALayer Animation


(†) Clipping is an important step for desired effect.

Swift 2 and earlier, see Steve Baughman's comment:
self.layer.addAnimation(animation, forKey: kCATransitionPush)

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

Speos answered 14/11, 2015 at 6:3 Comment(8)
Argh, this no longer works in Swift 3. Will report back when I find a fix.Johen
Works fine for me in Swift 3. Only have to change one line: self.layer.add(animation, forKey: kCATransitionPush)Antecede
Is there anyway to do this with a spring-push animation?Scaramouch
@Speos , what tool did make this gif with? Would you please kindly tell me? I have been searching for a long time.Heat
@0xfba I've used screengif to great effect. github.com/dergachev/screengifTachograph
@AdamWaite: GitHub project maintained to latest SwiftSpeos
+1, found link to this answer from GitHub. This actually works really well with other views as well, even with UITableViews.Expansionism
Thank you for this amazing simple solution. I noticed some unwanted animation flickering when setting the label's text immediately after calling pushTransition(). So I delayed updating the text by 0.01 seconds. Looks like a charm!Daltondaltonism
S
7

Objective-C

The Swift UIView Extension can be solved in Objective-C with an Interface Extension.

1. The Interface Extension

@interface UIView (SO33632266)
- (void)pushTransition:(CFTimeInterval)duration;
@end

@implementation UIView (SO33632266)
// Usage: insert [view pushTransition:]; right before changing content
- (void)pushTransition:(CFTimeInterval)duration
{
    CATransition *animation = [CATransition new];
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animation.type = kCATransitionPush;
    animation.subtype = kCATransitionFromTop;
    animation.duration = duration;
    [self.layer addAnimation:animation forKey:kCATransitionPush];
}
@end

2. pushTransition Invocation

[aLabel pushTransition:0.4];
aLabel.text = [[NSString alloc] initWithFormat:@"%ld", (long) ++self.count];

3. In Action

UILabel animation


One-off: without Interface Extension:

CATransition *animation = [CATransition animation];
animation.timingFunction = [CAMediaTimingFunction
    functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionPush;
animation.subtype = kCATransitionFromTop;
animation.duration = 0.4;
[aLabel.layer addAnimation:animation forKey:@"kCATransitionPush"];

Invocation:

aLabel.text = [[NSString alloc] initWithFormat:@"%ld", (long) ++self.count];
Speos answered 14/11, 2015 at 6:38 Comment(0)
E
3

QuartzCore provides a CATransition class for animated transition operations. CATransition has the following definition:

The CATransition class implements transition animations for a layer. You can specify the transition effect from a set of predefined transitions or by providing a custom CIFilter instance.


In order to have your label's text transition animated by pushing upwards the old text, you'll have to create an instance of CATransition and set its type to kCATransitionPush and its subtype to kCATransitionFromTop:

let animation = CATransition()
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.type = kCATransitionPush
animation.subtype = kCATransitionFromTop
animation.duration = 1

You will then be able to animate your label's text transition by adding the animation to your label's layer:

label.layer.add(animation, forKey: nil)
label.text = "Some new content"

The following Swift 3 Playground code shows a possible implementation in order to animate a UILabel's text transition using CATransition:

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {  
    let label: UILabel = {
        $0.frame.origin = CGPoint(x: 50, y: 50)
        $0.text = "Bob"
        $0.sizeToFit()
        return $0
    }(UILabel())
    let animation: CATransition = {
        $0.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        $0.type = kCATransitionPush
        $0.subtype = kCATransitionFromTop
        $0.duration = 1
        return $0
    }(CATransition())

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(label)

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

    func toggle(_ sender: UITapGestureRecognizer) {
        label.layer.add(animation, forKey: nil)
        label.text = label.text == "Bob" ? "Dan" : "Bob"
        label.sizeToFit()
    }
}

let controller = ViewController()
PlaygroundPage.current.liveView = controller
Eyespot answered 11/12, 2016 at 23:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.