web-dev-qa-db-fra.com

Comment animer un nombre incrémentant dans UILabel

J'ai une étiquette indiquant un numéro et je souhaite le remplacer par un chiffre plus élevé, mais j'aimerais ajouter un peu de lumière .. __J'aimerais que le numéro augmente progressivement jusqu'au numéro le plus élevé. une courbe d’inaccélération afin d’accélérer puis d’accélérer puis ralentir .. Cette réponse montre comment l’incrémenter (la 2ème réponse, pas la réponse acceptée) mais je l’animerais plutôt pour pouvoir l’augmenter légèrement. puis réduisez à nouveau ainsi que la courbe de facilité d’installation . comment faire une animation de partition en cours d’exécution dans iphone sdk

Des idées sur la meilleure façon de réaliser cela? Merci

Les numéros de début/fin seront saisis par l'utilisateur et je veux qu'il incrémente le numéro de fin dans le même temps. Donc, si je dois commencer 10 fin 100 ou commencer 10 000 fin, je veux qu'il compte jusqu'à la fin nombre en 5 secondes.

33
Darren

Vous pouvez utiliser un drapeau pour voir s’il doit monter ou descendre . Au lieu d’une boucle for, utilisez une boucle while . De cette manière, vous créez une boucle qui continue, vous devez donc trouver un moyen de l'arrêter aussi, fe par un bouton appuyez sur.

4
Maarten Kesselaers

J'ai en fait fait une telle classe juste pour cela appelé UICountingLabel:

http://github.com/dataxpress/UICountingLabel

Il vous permet de spécifier si vous souhaitez que le mode de comptage soit linéaire, facile à entrer, facile à utiliser, ou facile à entrer/sortir. Facilité d’entrée/sortie commence à compter lentement, s’accélère puis s’achève lentement, le tout dans le temps que vous spécifiez. 

Il ne prend actuellement pas en charge la définition de la taille de police réelle de l'étiquette en fonction de la valeur actuelle, bien que je puisse l'ajouter si c'est une fonctionnalité en demande. La plupart des étiquettes de mes mises en page n'ont pas beaucoup de place pour se développer ou se réduire, donc je ne sais pas comment vous voulez l'utiliser. Cependant, il se comporte totalement comme une étiquette normale, vous pouvez donc également modifier vous-même la taille de la police.

65
Tim

Vous pouvez utiliser GCD pour décaler les retards vers les threads d'arrière-plan.

Voici l'exemple d'animation de valeur (de 1 à 100 en 10 secondes)

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];
        });
    }
});
17
malex

Voici la réponse de @ malex dans 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)"
            }
        }
    }
}

Cependant, je recommande fortement de simplement télécharger cette classe à partir de GitHub et de la faire glisser dans votre projet. Je me suis servie de cette classe car le timing de mon code ne semble pas s’ajuster correctement pour les nombres plus faibles/plus élevés. Cette classe fonctionne très bien et est très belle. Voir cet article moyen pour référence.

11
Travis M.

Swift 4 Code

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)"
            })
        }
    })
0
kishor soneji

Détails

Xcode 9.2, Swift 4

Solution

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

Usage

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

Échantillon complet

N'oubliez pas de ajouter le code de solution ici

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

Résultats

 enter image description here

0
Vasily Bodnarchuk

Voici comment je l'ai fait:

- (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
Simon

Swift 4 Code:

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)"
            })
        }
    })
0
kishor soneji

Vous pouvez également vérifier https://github.com/leszek-s/LSCategories

Cela permet d'incrémenter/décrémenter un nombre dans UILabel avec une seule ligne de code comme celle-ci:

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