Animar alteração de texto no UILabel

133

Estou definindo um novo valor de texto para a UILabel. Atualmente, o novo texto aparece perfeitamente. No entanto, gostaria de adicionar alguma animação quando o novo texto aparecer. Estou imaginando o que posso fazer para animar a aparência do novo texto.

Joo Park
fonte
Para o Swift 5, veja minha resposta que mostra duas maneiras diferentes de resolver seu problema.
Imanou Petit 07/05/19

Respostas:

177

Gostaria de saber se funciona, e funciona perfeitamente!

Objetivo-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
fonte
16
Observe que o TransitionCrossDissolve é a chave para este trabalho.
akaru
7
Você não precisa de um "eu fraco" nos blocos de animação do UIView. Veja stackoverflow.com/a/27019834/511299
Sunkas
162

Objetivo-C

Para obter uma verdadeira transição de dissolução cruzada (rótulo antigo desaparecendo enquanto novo rótulo desaparecendo), você não deseja que o brilho se torne invisível. Isso resultaria em tremulação indesejada, mesmo que o texto não seja alterado .

Use esta abordagem:

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"

Veja também: Animar texto UILabel entre dois números?

Demonstração no iOS 10, 9, 8:

Em branco, transição de 1 a 5 desbotamento


Testado com o Xcode 8.2.1 e 7.1 , ObjectiveC no iOS 10 a 8.0 .

► Para baixar o projeto completo, pesquise SO-3073520 em Swift Recipes .

SwiftArchitect
fonte
Quando eu defino duas etiquetas ao mesmo tempo, ela pisca no simulador.
huggie
1
Possivelmente mal utilizado? O objetivo da minha abordagem é não usar 2 rótulos. Você usa um único rótulo -addAnimation:forKeypara esse rótulo e altera o texto do rótulo.
precisa saber é o seguinte
@ConfusedVorlon, verifique sua declaração. e verifique o projeto publicado em swiftarchitect.com/recipes/#SO-3073520 .
precisa saber é o seguinte
Eu posso ter entendido errado no passado. Eu pensei que você poderia definir a transição uma vez para a camada, depois fazer alterações subseqüentes e obter um desbotamento 'grátis'. Parece que você precisa especificar o desbotamento a cada alteração. Então, desde que você chame a transição a cada vez, isso funciona bem no meu teste no iOS9.
Vorlon confuso
Remova o engano, mas não parece funcionar no iOS9, se achar que não é mais apropriado. Obrigado.
SwiftArchitect
134

Swift 4

A maneira correta de desvanecer um UILabel (ou qualquer outro UIView) é usar a Core Animation Transition. Isso não piscará, nem ficará preto se o conteúdo não for alterado.

Uma solução portátil e limpa é usar um Extensionno Swift (invoque elementos visíveis de alteração anterior)

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

A invocação é assim:

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

Em branco, transição de 1 a 5 desbotamento


► Encontre esta solução no GitHub e detalhes adicionais sobre o Swift Recipes .

SwiftArchitect
fonte
1
Olá eu usei o seu código no meu projeto, pensei que você poderia estar interessado: github.com/goktugyil/CozyLoadingActivity
Esqarrouth
Nice - Se você pudesse usar a licença MIT (versão advogado-talk dessa mesma licença), que poderia ser usado em aplicativos comerciais ...
SwiftArchitect
Eu realmente não entendo sobre o material da licença. O que impede as pessoas de usá-lo em aplicativos comerciais agora?
precisa saber é o seguinte
2
Muitas empresas não podem usar licenças padrão, a menos que estejam listadas no opensource.org/licenses e algumas incorporam apenas Cocoapods aderentes ao opensource.org/licenses/MIT . Essa MITlicença garante que seu Cocoapod pode ser usado livremente por todos e qualquer pessoa.
SwiftArchitect
2
O problema com a licença WTF atual é que ela não cobre seus motivos: para iniciantes, ela não o reivindica como autor (direitos autorais) e, como tal, não prova que você pode conceder privilégios. Na Licença MIT, você primeiro reivindica a propriedade, que depois usa para renunciar aos direitos. Você realmente deve ler o MIT se quiser que os profissionais aproveitem seu código e participe do seu esforço de código aberto.
SwiftArchitect
20

desde o iOS4, obviamente isso pode ser feito com blocos:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Mapedd
fonte
11
Não é uma transição cross-fade.
SwiftArchitect
17

Aqui está o código para fazer isso funcionar.

[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
fonte
3
Isto não é um desvanecimento. É um desbotamento, desaparece completamente e depois desbota. É basicamente um tremor em câmera lenta, mas ainda é um tremor.
SwiftArchitect
18
por favor, não nomeie seu UILabels lbl
rigdonmr 10/10
5

Solução de extensão 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)
  }

}

Função chamada simplesmente por

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

Obrigado por todas as dicas caras. Espero que isso ajude a longo prazo

Muhammad Asyraf
fonte
4

Swift 4.2 versão da solução SwiftArchitect acima (funciona muito bem):

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

Invocação:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"
winnie-ru
fonte
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
fonte
1
O primeiro funciona, mas o segundo apenas conclui imediatamente, independentemente da duração que eu passar. Outra questão é que não consigo pressionar nenhum botão enquanto estou animando com a primeira solução.
endavid
Para permitir interações durante a animação, adicionei uma opção, opções: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid
Na primeira opção, o uso de self.view altera toda a exibição, isso significa que somente o TransitionCrossDissolve pode ser usado. Em vez disso, é melhor fazer a transição apenas do texto: "UIView.transitionWithView (self.sampleLabel, duration: 1.0 ...". Também no meu caso, funcionou melhor com "..., conclusão: nil)".
Vitalii 20/08/16
3

Com o Swift 5, você pode escolher um dos dois exemplos de código do Playground a seguir para animar as UILabelalterações de texto com algumas animações de dissolução cruzada.


# 1 Usando UIViewo transition(with:duration:options:animations:completion:)método de 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 Usando CATransitione CALayerdo 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
fonte
Ambas as animações funcionam apenas como crossDissolve. De qualquer forma, obrigado por uma resposta tão descritiva.
Yash Bedi 17/03
2

Solução Swift 4.2 (usando a resposta 4.0 e atualizando para novas enumerações 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
fonte
2

Os valores padrão do sistema de 0,25 para duratione .curveEaseInEaseOut para timingFunctiongeralmente são preferíveis para consistência entre as animações e podem ser omitidos:

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

que é o mesmo que escrever isso:

let animation = CATransition()
animation.duration = 0.25
animation.timingFunction = .curveEaseInEaseOut
label.layer.add(animation, forKey: nil)
label.text = "New text"
esquilo invisível
fonte
1

Esse é um método de extensão C # UIView baseado no código do @ SwiftArchitect. Quando o layout automático está envolvido e os controles precisam se mover, dependendo do texto do rótulo, esse código de chamada usa a Superview do rótulo como a visualização de transição, em vez do próprio rótulo. Eu adicionei uma expressão lambda para a ação para torná-la mais encapsulada.

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 chamada:

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

Se você quiser fazer isso Swiftcom atraso, tente o seguinte:

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

                    })
                }

        })
    }

usando a seguinte função criada 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 tornará isso no Swift 3

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

Há mais uma solução para conseguir isso. Foi descrito aqui . A ideia é subclassificar UILabele substituir a action(for:forKey:)função da seguinte maneira:

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

Exemplos 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
fonte