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.

Joo Park
la source
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)
Anton Gaenko
la source
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 .

SwiftArchitect
la source
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 .

SwiftArchitect
la source
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;
                 }];
Mapedd
la source
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];
Joo Park
la source
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

Muhammad Asyraf
la source
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"
winnie-ru
la source
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
})
AG
la source
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
Imanou Petit
la source
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"
}
Chris
la source
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"
écureuil invisible
la source
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";
  } );
Gary Z
la source
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)
}
ColossalChris
la source
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!"
Roman Aliyev
la source