Animare la modifica del testo in UILabel


133

Sto impostando un nuovo valore di testo su a UILabel. Attualmente, il nuovo testo appare bene. Tuttavia, vorrei aggiungere qualche animazione quando appare il nuovo testo. Mi chiedo cosa posso fare per animare l'aspetto del nuovo testo.


Per Swift 5, vedi la mia risposta che mostra 2 modi diversi per risolvere il tuo problema.
Imanou Petit

Risposte:


177

Mi chiedo se funziona e funziona perfettamente!

Objective-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
Si noti che TransitionCrossDissolve è la chiave per questo lavoro.
akaru,

7
Non è necessario [sé debole] nei blocchi di animazione di UIView. Vedere stackoverflow.com/a/27019834/511299
Sunkas

162

Objective-C

Per ottenere una vera transizione cross-dissolve (la vecchia etichetta sbiadisce mentre la nuova etichetta sbiadisce), non vuoi che la dissolvenza sia invisibile. Ciò comporterebbe uno sfarfallio indesiderato anche se il testo è invariato .

Utilizzare invece questo approccio:

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"

Vedi anche: Animare il testo UILabel tra due numeri?

Dimostrazione in iOS 10, 9, 8:

Transizione vuota, quindi da 1 a 5


Testato con Xcode 8.2.1 e 7.1 , ObjectiveC su iOS 10 a 8.0 .

► Per scaricare l'intero progetto, cercare SO-3073520 in Ricette Swift .


Quando imposto due etichette contemporaneamente, tuttavia, il simulatore emette uno sfarfallio.
Huggie,

1
Forse abusato? Il punto del mio approccio è di non usare 2 etichette. Si utilizza una singola etichetta, -addAnimation:forKeya tale etichetta, quindi si modifica il testo dell'etichetta.
SwiftArchitect,

@ConfusedVorlon, verifica la tua dichiarazione. e controlla il progetto pubblicato su swiftarchitect.com/recipes/#SO-3073520 .
SwiftArchitect,

Potrei aver avuto la parte sbagliata del bastone su questo in passato. Ho pensato che potresti definire la transizione una volta per il livello, quindi apportare le modifiche successive e ottenere una dissolvenza "libera". Sembra che devi specificare la dissolvenza ad ogni modifica. Quindi, purché tu chiami la transizione ogni volta, questo funziona bene nel mio test su iOS9.
Confuso Vorlon,

Rimuovi ciò che è fuorviante ma non sembra funzionare in iOS9 se ritieni che non sia più appropriato. Grazie.
SwiftArchitect,

134

Swift 4

Il modo corretto di sbiadire un UILabel (o qualsiasi UIView per quella materia) è usare un Core Animation Transition. Questo non sfarfallerà, né si spegnerà in nero se il contenuto è invariato.

Una soluzione portatile e pulita consiste nell'utilizzare a Extensionin Swift (invocare prima di cambiare elementi visibili)

// 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'invocazione è simile alla seguente:

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

Transizione vuota, quindi da 1 a 5


► Trova questa soluzione su GitHub e ulteriori dettagli sulle ricette Swift .


1
ciao ho usato il tuo codice nel mio progetto, ho pensato che potresti essere interessato: github.com/goktugyil/CozyLoadingActivity
Esqarrouth

Bello - Se potessi usare la licenza MIT (versione avvocato della stessa licenza), potrebbe essere utilizzata in app commerciali ...
SwiftArchitect,

Non capisco davvero le cose sulla licenza. Cosa impedisce alle persone di usarlo nelle app commerciali ora?
Esqarrouth,

2
Molte aziende non possono utilizzare le licenze standard a meno che non siano elencate su opensource.org/licenses e alcune incorporeranno solo Cocoapod aderenti a opensource.org/licenses/MIT . Tale MITlicenza garantisce che il tuo Cocoapod può essere utilizzato liberamente da chiunque e da chiunque.
SwiftArchitect,

2
Il problema con l'attuale Licenza WTF è che non copre i tuoi motivi: per cominciare, non ti rivendica come autore (copyright) e, come tale, non dimostra che puoi concedere i privilegi. Nella Licenza MIT, rivendichi prima la proprietà, che poi usi per rinunciare ai diritti. Dovresti davvero informarti sul MIT se vuoi che i professionisti sfruttino il tuo codice e partecipino alle tue attività open source.
SwiftArchitect,

20

da iOS4 può ovviamente essere fatto con blocchi:

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

11
Non è una transizione a dissolvenza incrociata.
SwiftArchitect,

17

Ecco il codice per farlo funzionare.

[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
Questa non è una dissolvenza. Questa è una dissolvenza, scompare del tutto, poi si dissolve. È fondamentalmente uno sfarfallio al rallentatore, ma è ancora uno sfarfallio.
SwiftArchitect,

18
per favore non nominare il tuo UILabels lbl
rigdonmr

5

UILabel Extension Solution

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

}

Funzione chiamata semplicemente da

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

Grazie per tutti i consigli ragazzi. Spero che questo possa aiutare a lungo termine


4

Versione di Swift 4.2 della soluzione di SwiftArchitect sopra (funziona alla grande):

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

Invocazione:

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

O

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

1
Il primo funziona, ma il secondo chiama subito il completamento, indipendentemente dalla durata che passo. Un altro problema è che non riesco a premere alcun pulsante mentre mi sto animando con la prima soluzione.
Endavid,

Per consentire le interazioni durante l'animazione, ho aggiunto un'opzione, opzioni: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid

Nella prima opzione, usare self.view modifica l'intera vista, ciò significa che è possibile utilizzare solo TransitionCrossDissolve. Invece è meglio trasferire solo il testo: "UIView.transitionWithView (self.sampleLabel, durata: 1.0 ...". Anche nel mio caso ha funzionato meglio con "..., completamento: zero)".
Vitalii,

3

Con Swift 5, puoi scegliere uno dei due seguenti esempi di codice Playground per animare le UILabelmodifiche al testo con alcune animazioni incrociate.


# 1. Usando UIViewil transition(with:duration:options:animations:completion:)metodo di classe

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. Utilizzando CATransitione CALayer's add(_:forKey:)il metodo

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

Entrambe le animazioni funzionano solo come crossDissolve. Comunque grazie per una risposta così descrittiva.
Yash Bedi

2

Soluzione Swift 4.2 (prendendo la risposta 4.0 e l'aggiornamento per la compilazione di nuovi enum)

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

I valori predefiniti di sistema di 0,25 per duratione .curveEaseInEaseOut per timingFunctionsono spesso preferibili per la coerenza tra le animazioni e possono essere omessi:

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

che è lo stesso di scrivere questo:

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

1

Questo è un metodo di estensione C # UIView basato sul codice di @ SwiftArchitect. Quando è coinvolto il layout automatico e i controlli devono spostarsi a seconda del testo dell'etichetta, questo codice chiamante utilizza la supervisione dell'etichetta come vista di transizione anziché l'etichetta stessa. Ho aggiunto un'espressione lambda per l'azione per renderla più incapsulata.

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

Prefisso telefonico:

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

0

Se vuoi farlo Swiftcon un ritardo, prova questo:

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

                    })
                }

        })
    }

utilizzando la seguente funzione creata da @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)
}

che diventerà questo in Swift 3

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

0

C'è un'altra soluzione per raggiungere questo obiettivo. È stato descritto qui . L'idea è la sottoclasse UILabele l'override della action(for:forKey:)funzione nel modo seguente:

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

Esempi di utilizzo:

// 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!"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.