Animuj zmianę tekstu w UILabel

135

Ustawiam nową wartość tekstową na UILabel. Obecnie nowy tekst wygląda dobrze. Chciałbym jednak dodać animację, gdy pojawi się nowy tekst. Zastanawiam się, co mogę zrobić, aby ożywić wygląd nowego tekstu.

Joo Park
źródło
W przypadku Swift 5 zobacz moją odpowiedź, która pokazuje 2 różne sposoby rozwiązania problemu.
Imanou Petit

Odpowiedzi:

181

Zastanawiam się, czy działa i działa idealnie!

Cel 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
źródło
16
Zauważ, że TransitionCrossDissolve jest kluczem do tego działania.
akaru
8
Nie potrzebujesz [słabego ja] w blokach animacji UIView. Zobacz stackoverflow.com/a/27019834/511299
Sunkas
162

Cel C

Aby osiągnąć prawdziwe przejście przez rozpuszczanie (stare etykiety zanikają, a nowe znikają), nie chcesz, aby zanikanie stało się niewidoczne. Spowodowałoby to niepożądane migotanie, nawet jeśli tekst byłby niezmieniony .

Zamiast tego użyj tego podejścia:

Zobacz także: Animowanie tekstu UILabel między dwoma liczbami?

Demonstracja w iOS 10, 9, 8:

Puste, następnie przejście od 1 do 5


Testowane z Xcode 8.2.1 i 7.1 , ObjectiveC na iOS 10 do 8.0 .

► Aby pobrać cały projekt, wyszukaj SO-3073520 w Swift Recipes .

SwiftArchitect
źródło
Kiedy ustawię dwie etykiety w tym samym czasie, na symulatorze migocze.
przytulanie
1
Prawdopodobnie nadużywane? Celem mojego podejścia jest nie używanie 2 etykiet. Używasz jednej etykiety, -addAnimation:forKeydo tej etykiety, a następnie zmieniasz tekst etykiety.
SwiftArchitect,
@ConfusedVorlon, sprawdź swoje oświadczenie. i sprawdź z opublikowanym projektem na swiftarchitect.com/recipes/#SO-3073520 .
SwiftArchitect
Być może w przeszłości miałem zły koniec kija. Pomyślałem, że możesz raz zdefiniować przejście dla warstwy, a następnie wprowadzić kolejne zmiany i uzyskać „darmowe” zanikanie. Wygląda na to, że musisz określić zanikanie przy każdej zmianie. Tak więc - pod warunkiem, że wywołasz przejście za każdym razem, w moim teście na iOS9 działa to dobrze.
Confused Vorlon
Usuń wprowadzające w błąd, ale wydaje się, że nie działa w systemie iOS9, jeśli uważasz, że nie jest już odpowiednie. Dziękuję Ci.
SwiftArchitect
135

Szybki 4

Właściwym sposobem na zanikanie UILabel (lub dowolnego UIView w tym przypadku) jest użycie pliku Core Animation Transition. To nie będzie migotać ani nie wyblaknie, jeśli zawartość pozostanie niezmieniona.

Przenośnym i czystym rozwiązaniem jest użycie języka ExtensionSwift (wywołanie przed zmianą widocznych elementów)

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

Inwokacja wygląda następująco:

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

Puste, następnie przejście od 1 do 5


► Znajdź to rozwiązanie w GitHub i dodatkowe informacje na temat Swift Recipes .

SwiftArchitect
źródło
1
cześć, użyłem twojego kodu w moim projekcie, myślę, że możesz być zainteresowany: github.com/goktugyil/CozyLoadingActivity
Esqarrouth
Fajnie - gdybyś mógł użyć licencji MIT (wersja prawnicza tej samej licencji), mogłaby być używana w aplikacjach komercyjnych ...
SwiftArchitect
Naprawdę nie rozumiem rzeczy związanych z licencją. Co powstrzymuje ludzi przed używaniem go teraz w aplikacjach komercyjnych?
Esqarrouth
2
Wiele firm nie może korzystać ze standardowych licencji, chyba że są wymienione na opensource.org/licenses, a niektóre będą włączać Cocoapods tylko zgodnie z opensource.org/licenses/MIT . Ta MITlicencja gwarantuje, że Twój Cocoapod może być swobodnie używany przez wszystkich i każdego.
SwiftArchitect
2
Problem z obecną licencją WTF polega na tym, że nie obejmuje ona twoich podstaw: po pierwsze, nie rości sobie praw do Ciebie jako autora (prawa autorskie) i jako taka nie udowadnia, że ​​możesz przekazać przywileje. W Licencji MIT najpierw zgłaszasz prawa własności, które następnie wykorzystujesz do zrzeczenia się praw. Naprawdę powinieneś poczytać o MIT, jeśli chcesz, aby profesjonaliści wykorzystali Twój kod i uczestniczyli w Twoim wysiłku związanym z open source.
SwiftArchitect
21

od iOS4 można to oczywiście zrobić za pomocą bloków:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Mapedd
źródło
11
Nie jest to przejściowe przejście.
SwiftArchitect,
17

Oto kod, dzięki któremu to zadziała.

[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
źródło
3
To nie jest zanik. To jest zanikanie, całkowite zniknięcie, a następnie zanikanie. Jest to zasadniczo migotanie w zwolnionym tempie, ale nadal jest to migotanie.
SwiftArchitect,
23
proszę nie nazywaj swoich etykiet UILabels lbl
rigdonmr
5

Rozwiązanie rozszerzenia 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)
  }

}

Funkcja Simply Called wg

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

Dzięki za wszystkie wskazówki. Mam nadzieję, że to pomoże w dłuższej perspektywie

Muhammad Asyraf
źródło
4

Wersja Swift 4.2 powyższego rozwiązania SwiftArchitect (działa świetnie):

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

Wezwanie:

    // This will fade

aLabel.fadeTransition(0.4)
aLabel.text = "text"
winnie-ru
źródło
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"
})

LUB

UIView.animateWithDuration(0.2, animations: {
    self.sampleLabel.alpha = 1
}, completion: {
    (value: Bool) in
    self.sampleLabel.alpha = 0.2
})
AG
źródło
1
Pierwsza działa, ale druga po prostu od razu wywołuje zakończenie, niezależnie od czasu, jaki mijam. Inną kwestią jest to, że nie mogę nacisnąć żadnego przycisku podczas animowania pierwszego rozwiązania.
endavid
W celu umożliwienia interakcji while animowanie I dodaje się opcję, opcje [.TransitionCrossDissolve, .AllowUserInteraction]
endavid
W pierwszej opcji za pomocą self.view zmienia się cały widok, co oznacza, że ​​można użyć tylko TransitionCrossDissolve. Zamiast tego lepiej jest zmienić tylko tekst: "UIView.transitionWithView (self.sampleLabel, duration: 1.0 ...". Również w moim przypadku działało lepiej z "..., complete: nil)".
Vitalii
3

Dzięki Swift 5 możesz wybrać jeden z dwóch poniższych przykładów kodu Playground, aby animować UILabelzmiany tekstu za pomocą animacji rozpuszczania krzyżowego.


# 1. Korzystanie UIViewz transition(with:duration:options:animations:completion:)metody klasy

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. Używanie CATransitioni CALayer„S add(_:forKey:)metody

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
źródło
Obie animacje działają tylko jak crossDissolve. W każdym razie dziękuję za tak opisową odpowiedź.
Yash Bedi
2

Rozwiązanie Swift 4.2 (pobranie odpowiedzi 4.0 i aktualizacja dla nowych wyliczeń do kompilacji)

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
źródło
2

Domyślne systemowe wartości 0,25 for durationi .curveEaseInEaseOut for timingFunctionsą często preferowane ze względu na spójność między animacjami i można je pominąć:

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

co jest tym samym, co napisanie tego:

let animation = CATransition()
animation.duration = 0.25
animation.timingFunction = .curveEaseInEaseOut
label.layer.add(animation, forKey: nil)
label.text = "New text"
niewidzialna wiewiórka
źródło
1

Jest to metoda rozszerzenia C # UIView oparta na kodzie @ SwiftArchitect. Kiedy używany jest układ automatyczny i kontrolki muszą się poruszać w zależności od tekstu etykiety, ten kod wywołujący używa Superviewu etykiety jako widoku przejścia zamiast samej etykiety. Dodałem wyrażenie lambda do akcji, aby było bardziej hermetyczne.

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

Kod telefoniczny:

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

Jeśli chcesz to zrobić Swiftz opóźnieniem, spróbuj tego:

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

                    })
                }

        })
    }

korzystając z funkcji stworzonej przez @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)
}

co stanie się tym w Swift 3

func delay(_ delay:Double, closure:()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.after(when: when, execute: closure)
}
ColossalChris
źródło
0

Jest jeszcze jedno rozwiązanie, aby to osiągnąć. Zostało to opisane tutaj . Pomysł polega na podklasowaniu UILabeli nadpisywaniu action(for:forKey:)funkcji w następujący sposób:

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

Przykłady użycia:

// 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
źródło