ios 如何在 UILabel 中为递增数字设置动画

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9251351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:48:13  来源:igfitidea点击:

How to animate incrementing number in UILabel

iphoneiosxcodeanimation

提问by Darren

I have a label showing a number and I want to change it to a higher number, however I'd like to add a bit of flare to it. I'd like to have the number increment up to the higher number with an ease inout curve so it speeds up then slows down. This answer shows how to make it increment (the 2nd answer, not the accepted answer) but I'd rather animate it so I could also make it increase in size slightly then shrink again as well as the ease inout curve. how to do a running score animation in iphone sdk

我有一个显示数字的标签,我想将其更改为更高的数字,但是我想为其添加一点耀斑。我想让数字增加到更高的数字,并带有一条缓和曲线,这样它就会先加速然后减速。这个答案显示了如何让它增加(第二个答案,不是接受的答案),但我宁愿动画它,所以我也可以让它的大小稍微增加然后再次缩小以及缓动输入曲线。 如何在iphone sdk中做跑分动画

Any ideas how best to achieve this? Thanks

任何想法如何最好地实现这一目标?谢谢

The start/end numbers will be user inputted and I want it to increment up the the end number in the same amount of time. So if I have start 10 end 100 or start 10 end 1000 I want it to count up to the end number in say 5 seconds.

开始/结束编号将由用户输入,我希望它在相同的时间内增加结束编号。因此,如果我有 start 10 end 100 或 start 10 end 1000,我希望它在 5 秒内计数到结束编号​​。

采纳答案by Maarten Kesselaers

You could use a flag to see if it has to go up or down. Instead of a for loop, use a while loop. In this way, you are creating a loop that keeps going, so you have to find a way to stop it also, f.e. by a button press.

您可以使用标志来查看它是否必须上升或下降。使用 while 循环代替 for 循环。通过这种方式,您正在创建一个不断运行的循环,因此您还必须找到一种方法来停止它,例如按下按钮。

回答by Tim

I actually made such a class just for this called UICountingLabel:

实际上,我专门为此创建了一个名为 UICountingLabel 的类:

http://github.com/dataxpress/UICountingLabel

http://github.com/dataxpress/UICountingLabel

It allows you to specify whether you want the counting mode to be linear, ease in, ease out, or ease in/out. Ease in/out starts counting slowly, speeds up, and then finishes slowly - all in whatever amount of time you specify.

它允许您指定计数模式是线性、缓入、缓出还是缓入/缓出。缓入/缓出开始缓慢计数,加速,然后缓慢完成 - 全部在您指定的时间内完成。

It doesn't currently support setting the actual font size of the label based on the current value, though I may add support for that if it's a feature that's in-demand. Most of the labels in my layouts don't have a lot of room to grow or shrink, so I'm not sure how you want to use it. However, it behaves totally like a normal label, so you can change the font size on your own as well.

它目前不支持根据当前值设置标签的实际字体大小,但如果它是一个需要的功能,我可能会添加支持。我的布局中的大多数标签都没有很大的增长或缩小空间,所以我不确定你想如何使用它。但是,它的行为完全像普通标签,因此您也可以自行更改字体大小。

回答by malex

You can use GCD to shift delays to background threads.

您可以使用 GCD 将延迟转移到后台线程。

Here is the example of value animation (from 1 to 100 in 10 seconds)

这是值动画的示例(10 秒内从 1 到 100)

float animationPeriod = 10;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    for (int i = 1; i < 101; i ++) {
        usleep(animationPeriod/100 * 1000000); // sleep in microseconds
        dispatch_async(dispatch_get_main_queue(), ^{
            yourLabel.text = [NSString stringWithFormat:@"%d", i];
        });
    }
});

回答by Travis M.

Here @malex's answer in swift 3.

这是@malex 在 swift 3 中的回答。

func incrementLabel(to endValue: Int) {
    let duration: Double = 2.0 //seconds
    DispatchQueue.global().async {
        for i in 0 ..< (endValue + 1) {
            let sleepTime = UInt32(duration/Double(endValue) * 1000000.0)
            usleep(sleepTime)
            DispatchQueue.main.async {
                self.myLabel.text = "\(i)"
            }
        }
    }
}

However, I strongly recommend simply downloading this class from GitHuband dragging it into your project, I've resorted to using it because the timing in my code doesn't seem to adjust properly for lower/higher count numbers. This class works great and looks very good. See this medium articlefor reference.

但是,我强烈建议您只需从 GitHub 下载此类并将其拖到您的项目中,我之所以使用它,是因为我的代码中的时间似乎没有针对较低/较高的计数数字进行适当调整。这门课效果很好,看起来很不错。请参阅此媒体文章以供参考。

回答by ahalps

If you want a fast counting animation, you can use a recursive func like so:

如果你想要一个快速计数的动画,你可以像这样使用递归函数:

func updateGems(diff: Int) {
     animateIncrement(diff: diff, index: 0, start: 50)
}

func animateIncrement(diff: Int, index: Int, start: Int) {

    if index == diff {return}
    gemsLabel.text = "\(start + index)"
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.002) {
        self.animateIncrement(diff: diff, index: index + 1, start: start)
    }
}

回答by Sergio

There you have it without blocking sleeps! stick this to a UILabel and it counts up and down from the value currently displaying, cleaning non numbers first. Adjust from Double to Int or Float if needed.

你有它而不会阻塞睡眠!将其粘贴到 UILabel 上,它从当前显示的值开始向上和向下计数,首先清除非数字。如果需要,从 Double 调整为 Int 或 Float。

yourlabel.countAnimation(upto: 100.0)
yourlabel.countAnimation(upto: 100.0)
another simple alternative

extension UILabel {    
    func countAnimation(upto: Double) {
        let from: Double = text?.replace(string: ",", replacement: ".").components(separatedBy: CharacterSet.init(charactersIn: "-0123456789.").inverted).first.flatMap { Double(
[self.label lsAnimateCounterWithStartValue:10 endValue:100 duration:5 completionBlock:nil];
) } ?? 0.0 let steps: Int = 20 let duration = 0.350 let rate = duration / Double(steps) let diff = upto - from for i in 0...steps { DispatchQueue.main.asyncAfter(deadline: .now() + rate * Double(i)) { self.text = "\(from + diff * (Double(i) / Double(steps)))" } } } }

回答by Leszek Szary

You can also check https://github.com/leszek-s/LSCategories

您还可以查看https://github.com/leszek-s/LSCategories

It allows incrementing/decrementing number in UILabel with a single line of code like this:

它允许使用如下一行代码在 UILabel 中增加/减少数字:

- (void)setupAndStartCounter:(CGFloat)duration {
    NSUInteger step = 3;//use your own logic here to define the step.
    NSUInteger remainder = numberYouWantToCount%step;//for me it was 30

    if (step < remainder) {
        remainder = remainder%step;
    }

    self.aTimer = [self getTimer:[self getInvocation:@selector(animateLabel:increment:) arguments:[NSMutableArray arrayWithObjects:[NSNumber numberWithInteger:remainder], [NSNumber numberWithInteger:step], nil]] timeInterval:(duration * (step/(float) numberYouWantToCountTo)) willRepeat:YES];
    [self addTimerToRunLoop:self.aTimer];
}

- (void)animateLabel:(NSNumber*)remainder increment:(NSNumber*)increment {
    NSInteger finish = finalValue;

    if ([self.aLabel.text integerValue] <= (finish) && ([self.aLabel.text integerValue] + [increment integerValue]<= (finish))) {
        self.aLabel.text = [NSString stringWithFormat:@"%lu",(unsigned long)([self.aLabel.text integerValue] + [increment integerValue])];
    }else{
        self.aLabel.text = [NSString stringWithFormat:@"%lu",(unsigned long)([self.aLabel.text integerValue] + [remainder integerValue])];
        [self.aTimer invalidate];
        self.aTimer = nil;
    }
}

#pragma mark -
#pragma mark Timer related Functions

- (NSTimer*)getTimer:(NSInvocation *)invocation timeInterval:(NSTimeInterval)timeInterval willRepeat:(BOOL)willRepeat
{
    return [NSTimer timerWithTimeInterval:timeInterval invocation:invocation repeats:willRepeat];
}

- (NSInvocation*)getInvocation:(SEL)methodName arguments:(NSMutableArray*)arguments
{
    NSMethodSignature *sig = [self methodSignatureForSelector:methodName];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
    [invocation setTarget:self];
    [invocation setSelector:methodName];
    if (arguments != nil)
    {
        id arg1 = [arguments objectAtIndex:0];
        id arg2 = [arguments objectAtIndex:1];
        [invocation setArgument:&arg1 atIndex:2];
        [invocation setArgument:&arg2 atIndex:3];
    }
    return invocation;
}

- (void)addTimerToRunLoop:(NSTimer*)timer
{
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

回答by Simon

This is how i did it :

我是这样做的:

let animationPeriod: Float = 1
    DispatchQueue.global(qos: .default).async(execute: {
        for i in 1..<Int(endValue) {
            usleep(useconds_t(animationPeriod / 10 * 10000)) // sleep in microseconds
            DispatchQueue.main.async(execute: {
                self.lbl.text = "\(i+1)"
            })
        }
    })

回答by kishor soneji

Swift 4 Code :

斯威夫特 4 代码:

class LoadingProcess {

    let minValue: Int
    let maxValue: Int
    var currentValue: Int

    private let progressQueue = DispatchQueue(label: "ProgressView")
    private let semaphore = DispatchSemaphore(value: 1)

    init (minValue: Int, maxValue: Int) {
        self.minValue = minValue
        self.currentValue = minValue
        self.maxValue = maxValue
    }

    private func delay(stepDelayUsec: useconds_t, completion: @escaping ()->()) {
        usleep(stepDelayUsec)
        DispatchQueue.main.async {
            completion()
        }
    }

    func simulateLoading(toValue: Int, step: Int = 1, stepDelayUsec: useconds_t? = 10_000,
                         valueChanged: @escaping (_ currentValue: Int)->(),
                         completion: ((_ currentValue: Int)->())? = nil) {

        semaphore.wait()
        progressQueue.sync {
            if currentValue <= toValue && currentValue <= maxValue {
                usleep(stepDelayUsec!)
                DispatchQueue.main.async {
                    valueChanged(self.currentValue)
                    self.currentValue += step
                    self.semaphore.signal()
                    self.simulateLoading(toValue: toValue, step: step, stepDelayUsec: stepDelayUsec, valueChanged: valueChanged, completion: completion)
                }

            } else {
                self.semaphore.signal()
                completion?(currentValue)
            }
        }
    }

    func finish(step: Int = 1, stepDelayUsec: useconds_t? = 10_000,
                valueChanged: @escaping (_ currentValue: Int)->(),
                completion: ((_ currentValue: Int)->())? = nil) {
        simulateLoading(toValue: maxValue, step: step, stepDelayUsec: stepDelayUsec, valueChanged: valueChanged, completion: completion)
    }
}

回答by Vasily Bodnarchuk

Details

细节

Xcode 9.2, swift 4

Xcode 9.2,快速 4

Solution

解决方案

let loadingProcess = LoadingProcess(minValue: 0, maxValue: 100)

loadingProcess.simulateLoading(toValue: 80, valueChanged: { currentValue in
    // Update views
})

DispatchQueue.global(qos: .background).async {
    print("Start loading data")
    sleep(5)
    print("Data loaded")
    loadingProcess.finish(valueChanged: { currentValue in
        // Update views
    }) { _ in
        print("End")
    }
}

Usage

用法

import UIKit

class ViewController: UIViewController {

    weak var counterLabel: UILabel!
    weak var progressView: UIProgressView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.alignment = .fill
        stackView.distribution = .fillProportionally
        stackView.spacing = 8
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 80).isActive = true
        stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -80).isActive = true

        let label = UILabel()
        label.textAlignment = .center
        label.text = "0"
        label.font = UIFont.systemFont(ofSize: 46)
        stackView.addArrangedSubview(label)
        counterLabel = label

        let progressView = UIProgressView()
        progressView.trackTintColor = .lightGray
        progressView.progressTintColor = .blue
        progressView.layer.cornerRadius = 4
        progressView.clipsToBounds = true
        progressView.heightAnchor.constraint(equalToConstant: 8).isActive = true
        stackView.addArrangedSubview(progressView)
        self.progressView = progressView

        let button = UIButton()
        button.setTitle("Start", for: .normal)
        button.addTarget(self, action: #selector(startButtonTapped), for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        button.heightAnchor.constraint(equalToConstant: 30).isActive = true
        stackView.addArrangedSubview(button)
    }

    @objc func startButtonTapped() {
        sample()
    }

    private func setProcess(currentValue: Int) {
        let value = 0.01 * Float(currentValue)
        self.counterLabel?.text = "\(currentValue)"
        self.progressView?.setProgress(value, animated: true)
        print("\(currentValue)")
    }

    func sample() {

        let loadingProcess = LoadingProcess(minValue: 0, maxValue: 100)

        loadingProcess.simulateLoading(toValue: 80, valueChanged: { currentValue in
            self.setProcess(currentValue: currentValue)
        })

        DispatchQueue.global(qos: .background).async {
            print("Start loading data")
            sleep(5)
            print("Data loaded")
            loadingProcess.finish(valueChanged: { currentValue in
                self.setProcess(currentValue: currentValue)
            }) { _ in
                print("end")
            }
        }
    }
}

Full sample

完整样品

Do not forget to add the solution code here

不要忘记在此处添加解决方案代码

##代码##

Results

结果

enter image description here

在此处输入图片说明