Анимировать изменение текста в UILabel

133

Я устанавливаю новое текстовое значение на UILabel. В настоящее время новый текст отображается нормально. Однако я хотел бы добавить анимацию при появлении нового текста. Мне интересно, что я могу сделать, чтобы оживить появление нового текста.

Джу Пак
источник
Для Swift 5 см. Мой ответ, в котором показаны 2 разных способа решения вашей проблемы.
Иману Пети

Ответы:

177

Интересно, работает ли он, и работает ли он отлично!

Objective-C

[UIView transitionWithView:self.label 
                  duration:0.25f 
                   options:UIViewAnimationOptionTransitionCrossDissolve 
                animations:^{

    self.label.text = rand() % 2 ? @"Nice nice!" : @"Well done!";

  } completion:nil];

Свифт 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
Обратите внимание, что TransitionCrossDissolve является ключом к этой работе.
akaru
7
Вам не нужно [слабое я] в блоках анимации UIView. См stackoverflow.com/a/27019834/511299
Sunkas
162

Objective-C

Для достижения истинного поперечного раствориться перехода (старый ярлык гаснуть в то время как новый лейбл выцветания), вы не хотите , чтобы замирание невидимым. Это может привести к нежелательному мерцанию, даже если текст не изменился .

Вместо этого используйте этот подход:

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"

Также смотрите: Анимировать текст UILabel между двумя числами?

Демонстрация в iOS 10, 9, 8:

Пусто, затем переход от 1 до 5


Протестировано с Xcode 8.2.1 и 7.1 , ObjectiveC на iOS с 10 по 8.0 .

► Чтобы загрузить проект полностью, найдите SO-3073520 в Swift Recipes .

SwiftArchitect
источник
Когда я устанавливаю две метки одновременно, на симуляторе мерцает.
huggie
1
Возможно злоупотребление? Суть моего подхода в том, чтобы не использовать 2 метки. Вы используете одну метку -addAnimation:forKeyдля этой метки, а затем меняете текст метки.
SwiftArchitect 07
@ConfusedVorlon, проверьте свое утверждение. и сверьтесь с опубликованным проектом на сайте swiftarchitect.com/recipes/#SO-3073520 .
SwiftArchitect
Возможно, я раньше заправлял это не тем концом палки. Я подумал, что вы можете определить переход один раз для слоя, затем внести последующие изменения и получить «бесплатное» затухание. Кажется, вам нужно указывать затухание при каждом изменении. Итак - при условии, что вы вызываете переход каждый раз, это отлично работает в моем тесте на iOS9.
Confused Vorlon
Удалите вводящую в заблуждение информацию, но она, похоже, не работает в iOS9, если вы считаете, что она больше не подходит. Спасибо.
SwiftArchitect
134

Swift 4

Правильный способ исчезновения UILabel (или любого другого UIView в этом отношении) - использовать файл Core Animation Transition. Он не будет мерцать и не станет черным, если содержимое не изменилось.

Портативное и чистое решение - использовать Extensionв Swift (вызывать предварительное изменение видимых элементов)

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

Вызов выглядит так:

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

Пусто, затем переход от 1 до 5


► Найдите это решение на GitHub и дополнительную информацию о Swift Recipes .

SwiftArchitect
источник
1
привет, я использовал ваш код в своем проекте, подумал, что вам может быть интересно: github.com/goktugyil/CozyLoadingActivity
Esqarrouth
Приятно - если бы вы могли использовать лицензию MIT (версия той же лицензии для адвокатов), ее можно было бы использовать в коммерческих приложениях ...
SwiftArchitect
Я ничего не понимаю в лицензионном материале. Что мешает людям использовать его в коммерческих приложениях сейчас?
Esqarrouth 05
2
Многие компании не могут использовать стандартные лицензии, если они не указаны на сайте opensource.org/licenses, а некоторые будут включать только Cocoapods в соответствии с opensource.org/licenses/MIT . Эта MITлицензия гарантирует, что ваш Cocoapod может свободно использоваться всеми и всеми.
SwiftArchitect 06
2
Проблема с текущей лицензией WTF заключается в том, что она не покрывает ваши основания: для начала, она не заявляет, что вы являетесь автором (авторские права), и, как таковая, не доказывает, что вы можете отдавать права. В лицензии MIT вы сначала заявляете о праве собственности, а затем используете ее для отказа от прав. Вам действительно стоит почитать о MIT, если вы хотите, чтобы профессионалы использовали ваш код и участвовали в ваших усилиях с открытым исходным кодом.
SwiftArchitect
20

начиная с iOS4, очевидно, что это можно сделать с помощью блоков:

[UIView animateWithDuration:1.0
                 animations:^{
                     label.alpha = 0.0f;
                     label.text = newText;
                     label.alpha = 1.0f;
                 }];
Mapedd
источник
11
Не плавный переход.
SwiftArchitect
17

Вот код, чтобы это работало.

[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
Это не увядание. Это постепенное исчезновение, полное исчезновение, затем постепенное исчезновение. Это в основном медленное мерцание, но это все еще мерцание.
SwiftArchitect
18
пожалуйста, не называйте свой UILabels lbl
rigdonmr
5

Решение расширения 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)
  }

}

Просто вызывается функцией

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

Спасибо за все советы, ребята. Надеюсь, это поможет в долгосрочной перспективе

Мухаммад Асыраф
источник
4

Версия Swift 4.2 решения SwiftArchitect выше (отлично работает):

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

Призвание:

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

ИЛИ

UIView.animateWithDuration(0.2, animations: {
    self.sampleLabel.alpha = 1
}, completion: {
    (value: Bool) in
    self.sampleLabel.alpha = 0.2
})
AG
источник
1
Первый работает, но второй просто вызывает завершение сразу, независимо от того, сколько времени я прохожу. Другая проблема заключается в том, что я не могу нажать ни одну кнопку во время анимации с первым решением.
endavid
Чтобы разрешить взаимодействие во время анимации, я добавил один параметр, options: [.TransitionCrossDissolve, .AllowUserInteraction]
endavid
В первом варианте использование self.view приводит к изменению всего представления, это означает, что можно использовать только TransitionCrossDissolve. Вместо этого лучше перенести только текст: «UIView.transitionWithView (self.sampleLabel, duration: 1.0 ...». Также в моем случае он работал лучше с «..., завершение: nil)».
Виталий
3

В Swift 5 вы можете выбрать один из двух следующих примеров кода Playground, чтобы анимировать изменения вашего UILabelтекста с помощью некоторой анимации перекрестного растворения.


# 1. Использование UIView«S transition(with:duration:options:animations:completion:)метод класса

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. Использование CATransitionи CALayer«S add(_:forKey:)метод

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
Иману Пети
источник
Обе анимации работают только как crossDissolve. В любом случае спасибо за такой подробный ответ.
Яш Беди,
2

Решение Swift 4.2 (получение ответа 4.0 и обновление для компиляции новых перечислений)

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

Системные значения по умолчанию 0,25 для durationи .curveEaseInEaseOut для timingFunctionчасто предпочтительны для единообразия анимации, и их можно опустить:

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

что то же самое, что написать это:

let animation = CATransition()
animation.duration = 0.25
animation.timingFunction = .curveEaseInEaseOut
label.layer.add(animation, forKey: nil)
label.text = "New text"
белка-невидимка
источник
1

Это метод расширения C # UIView, основанный на коде @ SwiftArchitect. Когда задействован автоматический макет и элементы управления необходимо перемещать в зависимости от текста метки, этот вызывающий код использует Superview метки в качестве представления перехода вместо самой метки. Я добавил лямбда-выражение для действия, чтобы сделать его более инкапсулированным.

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

Телефонный код:

  labelSuperview.FadeTransition( 0.5d, () =>
  {
    if ( condition )
      label.Text = "Value 1";
    else
      label.Text = "Value 2";
  } );
Гэри Зи
источник
0

Если вы хотите сделать это Swiftс опозданием, попробуйте следующее:

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

                    })
                }

        })
    }

используя следующую функцию, созданную @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)
}

который станет этим в Swift 3

func delay(_ delay:Double, closure:()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.after(when: when, execute: closure)
}
ColossalChris
источник
0

Для этого есть еще одно решение. Это было описано здесь . Идея состоит в том, чтобы создать подкласс UILabelи переопределить 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)
    }
}

Примеры использования:

// 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!"
Роман Алиев
источник