Animer le changement de texte dans UILabel


133

Je mets une nouvelle valeur de texte à a UILabel. Actuellement, le nouveau texte apparaît très bien. Cependant, j'aimerais ajouter une animation lorsque le nouveau texte apparaît. Je me demande ce que je peux faire pour animer l'apparence du nouveau texte.


Pour Swift 5, consultez ma réponse qui montre 2 façons différentes de résoudre votre problème.
Imanou Petit

Réponses:


177

Je me demande si ça marche, et ça marche parfaitement!

Objectif c

[UIView transitionWithView:self.label 
                  duration:0.25f 
                   options:UIViewAnimationOptionTransitionCrossDissolve 
                animations:^{

    self.label.text = rand() % 2 ? @"Nice nice!" : @"Well done!";

  } completion:nil];

Swift 3, 4, 5

UIView.transition(with: label,
              duration: 0.25,
               options: .transitionCrossDissolve,
            animations: { [weak self] in
                self?.label.text = (arc4random()() % 2 == 0) ? "One" : "Two"
         }, completion: nil)

16
Notez que TransitionCrossDissolve est la clé de ce fonctionnement.
akaru

7
Vous n'avez pas besoin de [soi faible] dans les blocs d'animation UIView. Voir stackoverflow.com/a/27019834/511299
Sunkas

162

Objectif c

Pour obtenir une véritable transition de fondu croisé (l'ancienne étiquette disparaît tandis que la nouvelle étiquette apparaît ), vous ne voulez pas que le fondu devienne invisible. Cela entraînerait un scintillement indésirable même si le texte reste inchangé .

Utilisez plutôt cette approche:

CATransition *animation = [CATransition animation];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.type = kCATransitionFade;
animation.duration = 0.75;
[aLabel.layer addAnimation:animation forKey:@"kCATransitionFade"];

// This will fade:
aLabel.text = "New"

Voir aussi: Animer du texte UILabel entre deux nombres?

Démonstration sous iOS 10, 9, 8:

Vide, puis transition de 1 à 5 fondus


Testé avec Xcode 8.2.1 et 7.1 , ObjectiveC sur iOS 10 à 8.0 .

► Pour télécharger le projet complet, recherchez SO-3073520 dans Swift Recipes .


Lorsque je place deux étiquettes en même temps, cela donne un scintillement sur le simulateur.
huggie

1
Peut-être mal utilisé? Le but de mon approche est de ne pas utiliser 2 étiquettes. Vous utilisez une seule étiquette, -addAnimation:forKeyà cette étiquette, puis modifiez le texte de l'étiquette.
SwiftArchitect

@ConfusedVorlon, veuillez vérifier votre déclaration. et comparez le projet publié à swiftarchitect.com/recipes/#SO-3073520 .
SwiftArchitect

J'ai peut-être eu le mauvais côté du bâton dans le passé. J'ai pensé que vous pouviez définir la transition une fois pour le calque, puis apporter des modifications ultérieures et obtenir un fondu «gratuit». Il semble que vous deviez spécifier le fondu à chaque changement. Donc - à condition que vous appeliez la transition à chaque fois, cela fonctionne bien dans mon test sur iOS9.
Confused Vorlon

Veuillez supprimer les informations trompeuses, mais ne semblent pas fonctionner dans iOS9 si vous pensez que cela n'est plus approprié. Je vous remercie.
SwiftArchitect

134

Swift 4

La bonne façon de fondre un UILabel (ou tout autre UIView d'ailleurs) est d'utiliser un fichier Core Animation Transition. Cela ne scintillera pas et ne passera pas au noir si le contenu reste inchangé.

Une solution portable et propre consiste à utiliser un Extensiondans Swift (invoquer les éléments visibles modifiés auparavant)

// Usage: insert view.fadeTransition right before changing content


extension UIView {
    func fadeTransition(_ duration:CFTimeInterval) {
        let animation = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name:
            CAMediaTimingFunctionName.easeInEaseOut)
        animation.type = CATransitionType.fade
        animation.duration = duration
        layer.add(animation, forKey: CATransitionType.fade.rawValue)
    }
}

L'appel ressemble à ceci:

// This will fade
aLabel.fadeTransition(0.4)
aLabel.text = "text"

Vide, puis transition de 1 à 5 fondus


► Trouvez cette solution sur GitHub et des détails supplémentaires sur les recettes Swift .


1
bonjour j'ai utilisé votre code dans mon projet, j'ai pensé que vous pourriez être intéressé: github.com/goktugyil/CozyLoadingActivity
Esqarrouth

Nice - Si vous pouviez utiliser la licence MIT (version avocat-talk de cette même licence), elle pourrait être utilisée dans des applications commerciales ...
SwiftArchitect

Je ne comprends pas vraiment les choses sur les licences. Qu'est-ce qui empêche les gens de l'utiliser dans des applications commerciales maintenant?
Esqarrouth

2
De nombreuses entreprises ne peuvent pas utiliser de licences standard à moins d'être répertoriées sur opensource.org/licenses et certaines n'intègreront que des Cocoapods adhérant à opensource.org/licenses/MIT . Cette MITlicence garantit que votre Cocoapod peut être utilisé librement par tout le monde.
SwiftArchitect

2
Le problème avec la licence WTF actuelle est qu'elle ne couvre pas votre terrain: pour commencer, elle ne vous revendique pas en tant qu'auteur (copyright), et en tant que telle, ne prouve pas que vous pouvez donner des privilèges. Dans la licence MIT, vous revendiquez d'abord la propriété, que vous utilisez ensuite pour renoncer à vos droits. Vous devriez vraiment lire sur le MIT si vous voulez que les professionnels exploitent votre code et participent à votre effort open source.
SwiftArchitect

20

depuis iOS4 cela peut évidemment être fait avec des blocs:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];

11
Pas une transition cross-fade.
SwiftArchitect

17

Voici le code pour que cela fonctionne.

[UIView beginAnimations:@"animateText" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:1.0f];
[self.lbl setAlpha:0];
[self.lbl setText:@"New Text";
[self.lbl setAlpha:1];
[UIView commitAnimations];

3
Ce n'est pas un fondu. C'est un fondu, une disparition complète, puis un fondu. C'est essentiellement un scintillement au ralenti, mais c'est toujours un scintillement.
SwiftArchitect

18
s'il vous plaît ne nommez pas votre UILabels lbl
rigdonmr

5

Solution d'extension UILabel

extension UILabel{

  func animation(typing value:String,duration: Double){
    let characters = value.map { $0 }
    var index = 0
    Timer.scheduledTimer(withTimeInterval: duration, repeats: true, block: { [weak self] timer in
        if index < value.count {
            let char = characters[index]
            self?.text! += "\(char)"
            index += 1
        } else {
            timer.invalidate()
        }
    })
  }


  func textWithAnimation(text:String,duration:CFTimeInterval){
    fadeTransition(duration)
    self.text = text
  }

  //followed from @Chris and @winnie-ru
  func fadeTransition(_ duration:CFTimeInterval) {
    let animation = CATransition()
    animation.timingFunction = CAMediaTimingFunction(name:
        CAMediaTimingFunctionName.easeInEaseOut)
    animation.type = CATransitionType.fade
    animation.duration = duration
    layer.add(animation, forKey: CATransitionType.fade.rawValue)
  }

}

Fonction simplement appelée par

uiLabel.textWithAnimation(text: "text you want to replace", duration: 0.2)

Merci pour tous les conseils les gars. J'espère que cela aidera à long terme


4

Version Swift 4.2 de la solution SwiftArchitect ci-dessus (fonctionne très bien):

    // Usage: insert view.fadeTransition right before changing content    

extension UIView {

        func fadeTransition(_ duration:CFTimeInterval) {
            let animation = CATransition()
            animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
            animation.type = CATransitionType.fade
            animation.duration = duration
            layer.add(animation, forKey: CATransitionType.fade.rawValue)
        }
    }

Invocation:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"

3

Swift 2.0:

UIView.transitionWithView(self.view, duration: 1.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
    self.sampleLabel.text = "Animation Fade1"
    }, completion: { (finished: Bool) -> () in
        self.sampleLabel.text = "Animation Fade - 34"
})

OU

UIView.animateWithDuration(0.2, animations: {
    self.sampleLabel.alpha = 1
}, completion: {
    (value: Bool) in
    self.sampleLabel.alpha = 0.2
})

1
Le premier fonctionne, mais le second appelle simplement l'achèvement tout de suite, quelle que soit la durée que je passe. Un autre problème est que je ne peux appuyer sur aucun bouton pendant que j'anime avec la première solution.
endavid

Pour permettre les interactions lors de l'animation, j'ai ajouté une option, les options: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid

Dans la première option, l'utilisation de self.view change la vue entière, cela signifie que seul TransitionCrossDissolve peut être utilisé. Au lieu de cela, il est préférable de faire la transition uniquement avec le texte: "UIView.transitionWithView (self.sampleLabel, duration: 1.0 ...". Dans mon cas également, cela fonctionnait mieux avec "..., completion: nil)".
Vitalii

3

Avec Swift 5, vous pouvez choisir l 'un des deux exemples de code Playground suivants afin d' animer les UILabelmodifications de votre texte avec une animation de dissolution croisée.


#1. Utilisation de UIViewla transition(with:duration:options:animations:completion:)méthode de classe de

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Car"

        view.backgroundColor = .white
        view.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggle(_:)))
        view.addGestureRecognizer(tapGesture)
    }

    @objc func toggle(_ sender: UITapGestureRecognizer) {
        let animation = {
            self.label.text = self.label.text == "Car" ? "Plane" : "Car"
        }
        UIView.transition(with: label, duration: 2, options: .transitionCrossDissolve, animations: animation, completion: nil)
    }

}

let controller = ViewController()
PlaygroundPage.current.liveView = controller

# 2. L' utilisation CATransitionet CALayerde add(_:forKey:)méthode

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let label = UILabel()
    let animation = CATransition()

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "Car"

        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        // animation.type = CATransitionType.fade // default is fade
        animation.duration = 2

        view.backgroundColor = .white
        view.addSubview(label)

        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toggle(_:)))
        view.addGestureRecognizer(tapGesture)
    }

    @objc func toggle(_ sender: UITapGestureRecognizer) {
        label.layer.add(animation, forKey: nil) // The special key kCATransition is automatically used for transition animations
        label.text = label.text == "Car" ? "Plane" : "Car"
    }

}

let controller = ViewController()
PlaygroundPage.current.liveView = controller

Les deux animations fonctionnent comme crossDissolve uniquement. En tout cas merci pour une réponse aussi descriptive.
Yash Bedi

2

Solution Swift 4.2 (prise de la réponse 4.0 et mise à jour des nouvelles énumérations à compiler)

extension UIView {
    func fadeTransition(_ duration:CFTimeInterval) {
        let animation = CATransition()
        animation.timingFunction = CAMediaTimingFunction(name:
            CAMediaTimingFunctionName.easeInEaseOut)
        animation.type = CATransitionType.fade
        animation.duration = duration
        layer.add(animation, forKey: CATransitionType.fade.rawValue)
    }
}

func updateLabel() {
myLabel.fadeTransition(0.4)
myLabel.text = "Hello World"
}

2

Les valeurs par défaut du système de 0,25 pour durationet .curveEaseInEaseOut pour timingFunctionsont souvent préférables pour la cohérence entre les animations et peuvent être omises:

let animation = CATransition()
label.layer.add(animation, forKey: nil)
label.text = "New text"

ce qui revient à écrire ceci:

let animation = CATransition()
animation.duration = 0.25
animation.timingFunction = .curveEaseInEaseOut
label.layer.add(animation, forKey: nil)
label.text = "New text"

1

Il s'agit d'une méthode d'extension C # UIView basée sur le code de @ SwiftArchitect. Lorsque la disposition automatique est impliquée et que les contrôles doivent se déplacer en fonction du texte de l'étiquette, ce code d'appel utilise la vue d'ensemble de l'étiquette comme vue de transition au lieu de l'étiquette elle-même. J'ai ajouté une expression lambda pour l'action afin de la rendre plus encapsulée.

public static void FadeTransition( this UIView AView, double ADuration, Action AAction )
{
  CATransition transition = new CATransition();

  transition.Duration = ADuration;
  transition.TimingFunction = CAMediaTimingFunction.FromName( CAMediaTimingFunction.Linear );
  transition.Type = CATransition.TransitionFade;

  AView.Layer.AddAnimation( transition, transition.Type );
  AAction();
}

Indicatif d'appel:

  labelSuperview.FadeTransition( 0.5d, () =>
  {
    if ( condition )
      label.Text = "Value 1";
    else
      label.Text = "Value 2";
  } );

0

Si vous souhaitez le faire Swiftavec un délai, essayez ceci:

delay(1.0) {
        UIView.transitionWithView(self.introLabel, duration: 0.25, options: [.TransitionCrossDissolve], animations: {
            self.yourLabel.text = "2"
            }, completion:  { finished in

                self.delay(1.0) {
                    UIView.transitionWithView(self.introLabel, duration: 0.25, options: [.TransitionCrossDissolve], animations: {
                        self.yourLabel.text = "1"
                        }, completion:  { finished in

                    })
                }

        })
    }

en utilisant la fonction suivante créée par @matt - https://stackoverflow.com/a/24318861/1982051 :

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

qui deviendra cela dans Swift 3

func delay(_ delay:Double, closure:()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.after(when: when, execute: closure)
}

0

Il existe une autre solution pour y parvenir. Cela a été décrit ici . L'idée est de sous UILabel-classer et de remplacer la action(for:forKey:)fonction de la manière suivante:

class LabelWithAnimatedText: UILabel {
    override var text: String? {
        didSet {
            self.layer.setValue(self.text, forKey: "text")
        }
    }

    override func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == "text" {
            if let action = self.action(for: layer, forKey: "backgroundColor") as? CAAnimation {
                let transition = CATransition()
                transition.type = kCATransitionFade

                //CAMediatiming attributes
                transition.beginTime = action.beginTime
                transition.duration = action.duration
                transition.speed = action.speed
                transition.timeOffset = action.timeOffset
                transition.repeatCount = action.repeatCount
                transition.repeatDuration = action.repeatDuration
                transition.autoreverses = action.autoreverses
                transition.fillMode = action.fillMode

                //CAAnimation attributes
                transition.timingFunction = action.timingFunction
                transition.delegate = action.delegate

                return transition
            }
        }
        return super.action(for: layer, forKey: event)
    }
}

Exemples d'utilisation:

// do not forget to set the "Custom Class" IB-property to "LabelWithAnimatedText"
// @IBOutlet weak var myLabel: LabelWithAnimatedText!
// ...

UIView.animate(withDuration: 0.5) {
    myLabel.text = "I am animated!"
}
myLabel.text = "I am not animated!"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.