Animieren Sie die Textänderung in UILabel

133

Ich setze einen neuen Textwert auf a UILabel. Derzeit erscheint der neue Text einwandfrei. Ich möchte jedoch eine Animation hinzufügen, wenn der neue Text angezeigt wird. Ich frage mich, was ich tun kann, um das Erscheinungsbild des neuen Textes zu animieren.

Joo Park
quelle
Für Swift 5 siehe meine Antwort , die zwei verschiedene Möglichkeiten zur Lösung Ihres Problems zeigt.
Imanou Petit

Antworten:

177

Ich frage mich, ob es funktioniert und es funktioniert perfekt!

Ziel 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
quelle
16
Beachten Sie, dass TransitionCrossDissolve der Schlüssel zu dieser Arbeit ist.
Akaru
7
In UIView-Animationsblöcken benötigen Sie kein [schwaches Selbst]. Siehe stackoverflow.com/a/27019834/511299
Sunkas
162

Ziel c

Um einen echten Cross-Dissolve-Übergang zu erzielen (Ausblenden des alten Etiketts während Einblenden des neuen Etiketts), möchten Sie nicht, dass das Einblenden unsichtbar wird. Dies würde zu unerwünschtem Flimmern führen, selbst wenn der Text unverändert bleibt .

Verwenden Sie stattdessen diesen Ansatz:

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"

Siehe auch: UILabel-Text zwischen zwei Zahlen animieren?

Demonstration in iOS 10, 9, 8:

Leer, dann 1 bis 5 Überblendungsübergang


Getestet mit Xcode 8.2.1 & 7.1 , ObjectiveC unter iOS 10 bis 8.0 .

► Um das vollständige Projekt herunterzuladen, suchen Sie in Swift Recipes nach SO-3073520 .

SwiftArchitect
quelle
Wenn ich zwei Beschriftungen gleichzeitig setze, flackert der Simulator jedoch.
Huggie
1
Möglicherweise missbraucht? Der Punkt meines Ansatzes ist, keine 2 Etiketten zu verwenden. Sie verwenden ein einzelnes Etikett -addAnimation:forKeyfür dieses Etikett und ändern dann den Text des Etiketts.
SwiftArchitect
@ConfusedVorlon, bitte überprüfen Sie Ihre Aussage. und überprüfen Sie das veröffentlichte Projekt unter swiftarchitect.com/recipes/#SO-3073520 .
SwiftArchitect
Möglicherweise habe ich in der Vergangenheit das falsche Ende des Stabs erreicht. Ich dachte, Sie könnten den Übergang einmal für die Ebene definieren, dann nachfolgende Änderungen vornehmen und eine "freie" Überblendung erhalten. Es scheint, dass Sie die Überblendung bei jeder Änderung angeben müssen. Vorausgesetzt, Sie rufen den Übergang jedes Mal auf, funktioniert dies in meinem Test unter iOS9 einwandfrei.
Verwirrter Vorlon
Bitte entfernen Sie die Irreführung , scheint aber in iOS9 nicht zu funktionieren, wenn Sie der Meinung sind, dass dies nicht mehr angemessen ist. Danke dir.
SwiftArchitect
134

Swift 4

Der richtige Weg, um ein UILabel (oder ein anderes UIView) auszublenden, ist die Verwendung von a Core Animation Transition. Dies flackert nicht und wird auch nicht schwarz, wenn der Inhalt unverändert bleibt.

Eine tragbare und saubere Lösung ist die Verwendung von a Extensionin Swift (Aufruf vor dem Ändern sichtbarer Elemente)

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

Der Aufruf sieht folgendermaßen aus:

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

Leer, dann 1 bis 5 Überblendungsübergang


► Finden Sie diese Lösung auf GitHub und weitere Details zu Swift Recipes .

SwiftArchitect
quelle
1
Hallo, ich habe Ihren Code in meinem Projekt verwendet, dachte, Sie könnten interessiert sein: github.com/goktugyil/CozyLoadingActivity
Esqarrouth
Nizza - Wenn Sie die MIT-Lizenz (Lawyer-Talk-Version derselben Lizenz) verwenden könnten, könnte sie in kommerziellen Apps verwendet werden ...
SwiftArchitect
Ich verstehe das Lizenzmaterial nicht wirklich. Was hindert die Leute daran, es jetzt in kommerziellen Apps zu verwenden?
Esqarrouth
2
Viele Unternehmen können keine Standardlizenzen verwenden, es sei denn, sie sind unter opensource.org/licenses aufgeführt, und einige Unternehmen enthalten nur Cocoapods, die sich an opensource.org/licenses/MIT halten . Diese MITLizenz garantiert, dass Ihr Cocoapod von jedem und jedem frei verwendet werden kann.
SwiftArchitect
2
Das Problem mit der aktuellen WTF-Lizenz ist, dass sie Ihre Gründe nicht abdeckt: Für den Anfang beansprucht sie Sie nicht als Autor (Urheberrecht) und beweist als solche nicht, dass Sie Privilegien vergeben können. In der MIT-Lizenz beanspruchen Sie zunächst das Eigentum, mit dem Sie dann Rechte aufgeben. Sie sollten sich wirklich über MIT informieren, wenn Sie möchten, dass Profis Ihren Code nutzen und an Ihren Open Source-Bemühungen teilnehmen.
SwiftArchitect
20

seit iOS4 kann es natürlich mit Blöcken gemacht werden:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Mapedd
quelle
11
Kein Cross-Fade-Übergang.
SwiftArchitect
17

Hier ist der Code, damit dies funktioniert.

[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
quelle
3
Dies ist keine Überblendung. Dies ist ein Ausblenden, das vollständig verschwindet und dann eingeblendet wird. Es ist im Grunde ein Zeitlupenflimmern, aber es ist immer noch ein Flackern.
SwiftArchitect
18
Bitte nennen Sie Ihre UILabels nicht lbl
rigdonmr
5

UILabel-Erweiterungslösung

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

}

Einfach aufgerufene Funktion von

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

Danke für all die Tipps Jungs. Hoffe das wird langfristig helfen

Muhammad Asyraf
quelle
4

Swift 4.2-Version der obigen SwiftArchitect-Lösung (funktioniert hervorragend):

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

Aufruf:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"
winnie-ru
quelle
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"
})

ODER

UIView.animateWithDuration(0.2, animations: {
    self.sampleLabel.alpha = 1
}, completion: {
    (value: Bool) in
    self.sampleLabel.alpha = 0.2
})
AG
quelle
1
Der erste funktioniert, aber der zweite ruft sofort die Fertigstellung auf, unabhängig von der Dauer, die ich verfolge. Ein weiteres Problem ist, dass ich keine Taste drücken kann, während ich mit der ersten Lösung animiere.
Endavid
Um Interaktionen während der Animation zuzulassen, habe ich eine Option hinzugefügt: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid
Bei der ersten Option mit self.view ändert sich die gesamte Ansicht. Dies bedeutet, dass nur TransitionCrossDissolve verwendet werden kann. Stattdessen ist es besser, nur den Text zu überführen: "UIView.transitionWithView (self.sampleLabel, Dauer: 1.0 ...". Auch in meinem Fall funktionierte es besser mit "..., Vervollständigung: Null)".
Vitalii
3

Mit Swift 5 können Sie eines der beiden folgenden Playground-Codebeispiele auswählen, um die UILabelTextänderungen Ihres Textes mit einer Cross-Dissolve-Animation zu animieren .


# 1. Verwenden UIViewder transition(with:duration:options:animations:completion:)Klassenmethode von

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. Verwenden CATransitionund CALayers‘ add(_:forKey:)Methode

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
quelle
Beide Animationen funktionieren nur wie crossDissolve. Trotzdem danke für eine so beschreibende Antwort.
Yash Bedi
2

Swift 4.2-Lösung (4.0-Antwort nehmen und aktualisieren, damit neue Aufzählungen kompiliert werden können)

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

Die Systemstandardwerte von 0,25 für durationund .curveEaseInEaseOut für timingFunctionsind aus Gründen der Konsistenz zwischen Animationen häufig vorzuziehen und können weggelassen werden:

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

Das ist das gleiche wie das Schreiben:

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

Dies ist eine C # UIView-Erweiterungsmethode, die auf dem Code von @ SwiftArchitect basiert. Wenn das automatische Layout beteiligt ist und die Steuerelemente abhängig vom Text des Etiketts verschoben werden müssen, verwendet dieser aufrufende Code die Übersicht des Etiketts als Übergangsansicht anstelle des Etiketts selbst. Ich habe einen Lambda-Ausdruck für die Aktion hinzugefügt, um sie gekapselt zu machen.

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

Aufrufcode:

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

Wenn Sie dies Swiftmit Verzögerung tun möchten, versuchen Sie Folgendes:

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

                    })
                }

        })
    }

Verwenden der folgenden von @matt erstellten Funktion - 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)
}

das wird dies in Swift 3

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

Es gibt noch eine Lösung, um dies zu erreichen. Es wurde hier beschrieben . Die Idee besteht darin, die Funktion folgendermaßen zu unterteilen UILabelund zu überschreiben action(for:forKey:):

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

Anwendungsbeispiele:

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