如何在 UILabel 中制作递增数字的动画

问题描述 投票:0回答:12

我有一个显示数字的标签,我想将其更改为更高的数字,但是我想为其添加一些耀斑。 我希望通过轻松输入曲线将数字递增到更高的数字,以便它加速然后减慢。 这个答案显示了如何使其增加(第二个答案,不是接受的答案),但我宁愿对其进行动画处理,这样我也可以使其大小稍微增加,然后再次缩小以及缓入输出曲线。 如何在iphone sdk中制作跑分动画

有什么想法可以最好地实现这一目标吗? 谢谢

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

ios objective-c iphone xcode animation
12个回答
77
投票

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

http://github.com/dataxpress/UICountingLabel

它允许您指定计数模式是否为线性、缓入、缓出或缓入/缓出。 缓入/缓出开始缓慢计数,加速,然后缓慢完成 - 无论您指定多少时间。

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


20
投票

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

但是,我强烈建议简单地从 GitHub 下载此类并将其拖到您的项目中,我之所以使用它,是因为我的代码中的时间似乎无法针对较低/较高的计数数字进行正确调整。这个类效果很好并且看起来非常好。请参阅这篇中等文章以供参考。


18
投票
您可以使用 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]; }); } });
    

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


3
投票
有了它,就可以不妨碍睡眠了! 将其粘贴到 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)))" } } } }
    

1
投票
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)" }) } })
    

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

1
投票
对于那些正在寻找线性减速计数器(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)" } } } }

您可以根据需要更改静态值来更改起点和减慢速度;)


1
投票
我的

@Sergio答案的变体:

extension UILabel { func countAnimation(upto: Double) { let fromString = text?.replacingOccurrences(of: "Score: ", with: "") let from: Double = fromString?.replacingOccurrences(of: ",", with: ".") .components(separatedBy: CharacterSet.init(charactersIn: "-0123456789.").inverted) .first.flatMap { Double($0) } ?? 0.0 let duration = 0.4 let diff = upto - from let rate = duration / diff for item in 0...Int(diff) { DispatchQueue.main.asyncAfter(deadline: .now() + rate * Double(item)) { self.text = "Score: \(Int(from + diff * (Double(item) / diff)))" } } } }
因此步数取决于起始值和结束值之间的差异


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

0
投票
您还可以查看

https://github.com/leszek-s/LSCategories

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.