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

时间:2012-02-12 18:20:52

标签: iphone ios xcode animation

我有一个标签显示一个数字,我想将其更改为更高的数字,但是我想为它添加一些亮点。 我想让数字增加到更高的数字,并且容易进入曲线,因此它会加速然后变慢。 这个答案显示了如何使它增加(第二个答案,而不是接受的答案),但我宁愿为它制作动画,这样我也可以使它稍微增大,然后再缩小,以及缓和曲线。 how to do a running score animation in iphone sdk

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

开始/结束编号将由用户输入,我希望它在相同的时间内递增结束编号。因此,如果我已经开始10结束100或开始10结束1000我希望它在5秒钟内计算结束数字。

12 个答案:

答案 0 :(得分:66)

我实际上只是为了这个名为UICountingLabel的那个类:

http://github.com/dataxpress/UICountingLabel

它允许您指定是否要将计数模式设置为线性,缓入,缓出或缓入/缓出。轻松进入/退出开始缓慢计数,加速,然后慢慢结束 - 所有这些都在您指定的任何时间内完成。

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

答案 1 :(得分:17)

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

以下是值动画的示例(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];
        });
    }
});

答案 2 :(得分:11)

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

但是,我强烈建议简单地downloading this class from GitHub并将其拖入您的项目中,我已经使用它,因为我的代码中的时间似乎不适合更低/更高的计数数字。这个课程很棒,看起来很棒。请参阅this medium article以供参考。

答案 3 :(得分:4)

你可以使用一个标志来查看它是否必须上升或下降。 而不是for循环,使用while循环。 通过这种方式,你创建了一个继续前进的循环,所以你必须找到一种方法来阻止它,例如。按下按钮。

答案 4 :(得分:2)

在这里您就不会阻塞睡眠! 将其粘贴到UILabel上,它将根据当前显示的值上下计数,首先清除非数字。 如果需要,可以从Double调整为Int或Float。

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($0) } ?? 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)))"
            }
        }
    }
}

答案 5 :(得分:1)

如果您想要快速计数的动画,则可以使用递归函数,如下所示:

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

答案 6 :(得分:0)

我就这样做了:

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

答案 7 :(得分:0)

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

它允许使用单行代码在UILabel中递增/递减数字,如下所示:

[self.label lsAnimateCounterWithStartValue:10 endValue:100 duration:5 completionBlock:nil];

答案 8 :(得分:0)

详细

Xcode 9.2,swift 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)
    }
}

用法

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

完整样本

  

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

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

结果

enter image description here

答案 9 :(得分:0)

Swift 4代码

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

答案 10 :(得分:0)

Swift 4代码:

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

答案 11 :(得分:0)

对于正在寻找线性减速计数器(Swift 5)的用户:

    func animateCountLabel(to userCount: Int) {
        countLabel.text = " "
        let countStart: Int = userCount - 10
        let countEnd: Int = userCount
        DispatchQueue.global(qos: .default).async { [weak self] in
            for i in (countStart...countEnd) {
                let delayTime = UInt64(pow(2, (Float(i) - Float(countStart))))
                usleep(useconds_t( delayTime * 1300 )) // sleep in microseconds
                DispatchQueue.main.async { [weak self] in
                    self?.countLabel.text = "\(i)"
                }
            }
        }
    }

您可以通过根据需要更改静态值来更改起点和降低速度;)