我有一个显示数字的标签,我想将其更改为更高的数字,但是我想为其添加一些耀斑。 我希望通过轻松输入曲线将数字递增到更高的数字,以便它加速然后减慢。 这个答案显示了如何使其增加(第二个答案,不是接受的答案),但我宁愿对其进行动画处理,这样我也可以使其大小稍微增加,然后再次缩小以及缓入输出曲线。 如何在iphone sdk中制作跑分动画
有什么想法可以最好地实现这一目标吗? 谢谢
开始/结束数字将由用户输入,我希望它在相同的时间内增加结束数字。因此,如果我有 start 10 end 100 或 start 10 end 1000,我希望它在 5 秒内计数到结束数字。
我实际上专门为此创建了一个名为 UICountingLabel 的类:
http://github.com/dataxpress/UICountingLabel
它允许您指定计数模式是否为线性、缓入、缓出或缓入/缓出。 缓入/缓出开始缓慢计数,加速,然后缓慢完成 - 无论您指定多少时间。
它目前不支持根据当前值设置标签的实际字体大小,尽管如果这是一个需要的功能,我可能会添加对此的支持。 我的布局中的大多数标签没有太多的增长或收缩空间,所以我不确定你想如何使用它。 然而,它的行为完全像一个普通的标签,所以你也可以自己改变字体大小。
这是 @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 下载此类并将其拖到您的项目中,我之所以使用它,是因为我的代码中的时间似乎无法针对较低/较高的计数数字进行正确调整。这个类效果很好并且看起来非常好。请参阅这篇中等文章以供参考。
这是数值动画的示例(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];
});
}
});
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)))"
}
}
}
}
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)"
})
}
})
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)
}
}
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)"
}
}
}
}
您可以根据需要更改静态值来更改起点和减慢速度;)
@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)))"
}
}
}
}
因此步数取决于起始值和结束值之间的差异
- (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];
}
解决方案
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")
}
}
}
}
结果