Avatar ·

Как убрать нежелательную анимацию UIButton при изменении заголовка в iOS

Всем привет! Я новичок в iOS-разработке, и столкнулся со странной проблемой. Когда я программно меняю заголовок кнопки (например, при нажатии или по таймеру), происходит плавная анимация смены текста — он как бы «перетекает» из старого в новый. Это выглядит некрасиво, особенно если нужно быстро обновлять кнопку, например, в игровом интерфейсе или при смене статуса. Вот пример моего кода на Swift: ```swift class ViewController: UIViewController { @IBOutlet weak var myButton: UIButton! override func viewDidLoad() { super.viewDidLoad() myButton.setTitle("Нажми меня", for: .normal) } @IBAction func buttonTapped(_ sender: UIButton) { myButton.setTitle("Новый текст", for: .normal) } } ``` При нажатии на кнопку заголовок меняется с анимацией. Я хочу, чтобы текст менялся мгновенно, без плавного перехода. Пробовал оборачивать изменение в `UIView.performWithoutAnimation`, но это не помогает. Также пробовал установить `myButton.titleLabel?.layer.removeAllAnimations()` — эффекта нет. Подскажите, как полностью отключить эту анимацию? Использую Swift 5 и iOS 15+. <<>>

Причина анимации и базовое решение

Анимация смены заголовка UIButton — это встроенное поведение, которое UIKit применяет автоматически для обеспечения плавности интерфейса. Оно работает на уровне UIView и вызывается при изменении текста через setTitle(_:for:). Простой вызов UIView.performWithoutAnimation не всегда срабатывает, потому что анимация запускается внутри самого метода кнопки.

Вот проверенное решение, которое гарантированно отключает анимацию:

// Способ 1: Использование CATransaction
CATransaction.begin()
CATransaction.setDisableActions(true)
myButton.setTitle("Новый текст", for: .normal)
CATransaction.commit()

Этот код отключает все неявные анимации CALayer, включая анимацию заголовка. Работает на всех версиях iOS.

Более элегантный способ с расширением

Чтобы не писать один и тот же код каждый раз, создайте расширение для UIButton:

extension UIButton {
    func setTitleWithoutAnimation(_ title: String?, for state: UIControl.State) {
        UIView.performWithoutAnimation {
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            self.setTitle(title, for: state)
            self.layoutIfNeeded()
            CATransaction.commit()
        }
    }
}

Использование:

myButton.setTitleWithoutAnimation("Мгновенный текст", for: .normal)

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

Почему ваш код не работал?

  • UIView.performWithoutAnimation — блокирует анимации на уровне UIView, но анимация заголовка может быть на уровне CALayer, поэтому требуется дополнительный CATransaction.
  • titleLabel?.layer.removeAllAnimations() — удаляет текущие анимации, но не предотвращает запуск новых при следующем изменении текста.

Полный пример для вашего кода

class ViewController: UIViewController {
    @IBOutlet weak var myButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        myButton.setTitleWithoutAnimation("Нажми меня", for: .normal)
    }

    @IBAction func buttonTapped(_ sender: UIButton) {
        myButton.setTitleWithoutAnimation("Новый текст", for: .normal)
    }

    func setTitleWithoutAnimation(_ title: String?, for state: UIControl.State) {
        UIView.performWithoutAnimation {
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            self.myButton.setTitle(title, for: state)
            self.myButton.layoutIfNeeded()
            CATransaction.commit()
        }
    }
}

Этот код полностью убирает анимацию. Рекомендую вынести расширение в отдельный файл для переиспользования в других проектах.

<<>>

Альтернативный подход: работа с titleLabel напрямую

Если вам нужно только изменить текст без анимации, можно обратиться к titleLabel напрямую, минуя метод setTitle. Однако этот способ менее надёжен, так как не обновляет внутреннее состояние кнопки для разных состояний (normal, highlighted, disabled).

myButton.titleLabel?.text = "Новый текст"
myButton.titleLabel?.sizeToFit() // подгоняем размер, если нужно

Минусы:

  • Не работает для состояний, отличных от .normal.
  • Может не обновить размер кнопки автоматически.
  • При переиспользовании кнопки в таблице или коллекции могут возникнуть артефакты.

Когда использовать: Только для простых интерфейсов, где кнопка не меняет состояние и не требует точного соответствия UIControl.State.

Рекомендую всё же использовать первый ответ с CATransaction — он универсален и работает без побочных эффектов.