Cambio de texto animado en UILabel

133

Estoy configurando un nuevo valor de texto a a UILabel. Actualmente, el nuevo texto parece estar bien. Sin embargo, me gustaría agregar algo de animación cuando aparezca el nuevo texto. Me pregunto qué puedo hacer para animar la aparición del nuevo texto.

Joo Park
fuente
Para Swift 5, vea mi respuesta que muestra 2 formas diferentes de resolver su problema.
Imanou Petit

Respuestas:

177

Me pregunto si funciona, ¡y funciona perfectamente!

C objetivo

[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
fuente
16
Tenga en cuenta que TransitionCrossDissolve es la clave para este trabajo.
akaru
77
No necesita [yo débil] en los bloques de animación UIView. Ver stackoverflow.com/a/27019834/511299
Sunkas
162

C objetivo

Para lograr una verdadera transición de disolución cruzada (la etiqueta anterior se desvanece mientras la etiqueta nueva se desvanece), no desea que el desvanecimiento se vuelva invisible. Resultaría en un parpadeo no deseado incluso si el texto no se modifica .

Utilice este enfoque en su lugar:

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"

Ver también: ¿ Animar texto UILabel entre dos números?

Demostración en iOS 10, 9, 8:

En blanco, luego transición de desvanecimiento de 1 a 5


Probado con Xcode 8.2.1 y 7.1 , ObjectiveC en iOS 10 a 8.0 .

► Para descargar el proyecto completo, busque SO-3073520 en Swift Recipes .

Arquitecto Swift
fuente
Sin embargo, cuando configuro dos etiquetas al mismo tiempo, parpadea en el simulador.
Huggie
1
Posiblemente mal utilizado? El punto de mi enfoque es no usar 2 etiquetas. Utiliza una sola etiqueta, -addAnimation:forKeya esa etiqueta, luego cambia el texto de la etiqueta.
SwiftArchitect
@ConfusedVorlon, verifique su estado de cuenta. y verifique el proyecto publicado en swiftarchitect.com/recipes/#SO-3073520 .
Arquitecto Swift el
Puede que me haya equivocado en esto en el pasado. Pensé que podría definir la transición una vez para la capa, luego realizar cambios posteriores y obtener un desvanecimiento 'libre'. Parece que debe especificar el desvanecimiento en cada cambio. Entonces, siempre que llame a la transición cada vez, esto funciona bien en mi prueba en iOS9.
Confundido Vorlon
Elimine el engaño, pero no parece funcionar en iOS9 si cree que ya no es apropiado. Gracias.
Arquitecto Swift el
134

Swift 4

La forma correcta de desvanecer un UILabel (o cualquier UIView para el caso) es usar a Core Animation Transition. Esto no parpadeará, ni se desvanecerá a negro si el contenido no se modifica.

Una solución portátil y limpia es usar un Extensionen Swift (invocar antes de cambiar elementos visibles)

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

La invocación se ve así:

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

En blanco, luego transición de desvanecimiento de 1 a 5


► Encuentre esta solución en GitHub y detalles adicionales sobre Recetas Swift .

Arquitecto Swift
fuente
1
hola Usé
Esqarrouth
Niza: si pudieras usar la licencia MIT (versión de la misma licencia para hablar con un abogado), podría usarse en aplicaciones comerciales ...
SwiftArchitect
Realmente no entiendo sobre las cosas de la licencia. ¿Qué impide que las personas lo usen ahora en aplicaciones comerciales?
Esqarrouth
2
Muchas compañías no pueden usar licencias estándar a menos que se enumeren en opensource.org/licenses y algunas solo incorporarán Cocoapods que se adhieran a opensource.org/licenses/MIT . Esa MITlicencia garantiza que su Cocoapod puede ser utilizado libremente por todos y por cualquiera.
SwiftArchitect
2
El problema con la licencia WTF actual es que no cubre sus motivos: para empezar, no lo reclama como el autor (derechos de autor) y, como tal, no prueba que pueda otorgar privilegios. En la Licencia MIT, primero reclama la propiedad, que luego utiliza para renunciar a los derechos. Realmente debería leer sobre MIT si desea que los profesionales aprovechen su código y participen en su esfuerzo de código abierto.
SwiftArchitect
20

desde iOS4 obviamente se puede hacer con bloques:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Mapedd
fuente
11
No es una transición de fundido cruzado.
Arquitecto Swift
17

Aquí está el código para que esto funcione.

[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
fuente
3
Esto no es un desvanecimiento. Esto se desvanece, desaparece por completo, luego se desvanece. Es básicamente un parpadeo en cámara lenta, pero sigue siendo un parpadeo.
Arquitecto Swift
18
por favor no nombre su UILabels lbl
rigdonmr
5

Solución de extensión 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)
  }

}

Función simplemente llamada por

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

Gracias por todos los consejos chicos. Espero que esto ayude a largo plazo

Muhammad Asyraf
fuente
4

Versión Swift 4.2 de la solución de SwiftArchitect anterior (funciona muy 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)
        }
    }

Invocación:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"
winnie-ru
fuente
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
})
AG
fuente
1
El primero funciona, pero el segundo simplemente llama a la finalización de inmediato, independientemente de la duración que pase. Otro problema es que no puedo presionar ningún botón mientras estoy animando con la primera solución.
endavid
Para permitir interacciones durante la animación, agregué una opción, opciones: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid
En la primera opción, usar self.view hace que cambie toda la vista, esto significa que solo se puede usar TransitionCrossDissolve. En cambio, es mejor hacer la transición solo del texto: "UIView.transitionWithView (self.sampleLabel, duración: 1.0 ...". También en mi caso funcionó mejor con "..., finalización: nulo)".
Vitalii
3

Con Swift 5, puede elegir uno de los dos ejemplos de código de Playground siguientes para animar los UILabelcambios de texto con alguna animación de disolución cruzada.


# 1 Usando UIViewel transition(with:duration:options:animations:completion:)método de clase

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 El uso de CATransitiony CALayer's add(_:forKey:)método

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
fuente
Ambas animaciones funcionan como crossDissolve solamente. De todos modos, gracias por una respuesta tan descriptiva.
Yash Bedi
2

Solución Swift 4.2 (toma 4.0 respuestas y actualizaciones para nuevas enumeraciones para compilar)

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
fuente
2

Los valores predeterminados del sistema de 0.25 para durationy .curveEaseInEaseOut para a timingFunctionmenudo son preferibles para la coherencia entre las animaciones, y se pueden omitir:

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

que es lo mismo que escribir esto:

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

Este es un método de extensión C # UIView que se basa en el código de @ SwiftArchitect. Cuando el diseño automático está involucrado y los controles necesitan moverse según el texto de la etiqueta, este código de llamada usa la Supervisión de la etiqueta como la vista de transición en lugar de la etiqueta misma. Agregué una expresión lambda para la acción para hacerlo más encapsulado.

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

Código de llamada:

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

Si desea hacer esto Swiftcon retraso, intente esto:

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

                    })
                }

        })
    }

utilizando la siguiente función creada por @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)
}

que se convertirá en esto en Swift 3

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

Hay una solución más para lograr esto. Fue descrito aquí . La idea es subclasificar UILabely anular la action(for:forKey:)función de la siguiente manera:

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

Ejemplos de uso:

// 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
fuente