[Из песочницы] Кастомная кнопка с простой анимацией на Swift. Пример 1

Привет Хабр! В этой статье я покажу, как можно создать в Xcode свою кастомную кнопку с простой анимацией с помощью языка Swift. Совместимость: iOS8 или выше.

Это просто пример, поэтому всевозможные права доступа, типа private и т. п., здесь не используются. Также подразумевается, что вы уже знакомы с основами Xcode и Swift и сможете без труда создать начальный «Single View App»‑проект в среде Xcode, этот этап также будет опущен.

Кнопка будет выглядеть следующим образом:
imageНормальное состояние
imageНажатое состояние
imageАнимация

Подготовка


Добавляем в проект новый файл с незатейливым названием CustomButton1.swift, в котором будет находиться класс новой кнопки с таким же незатейливым названием, наследуемый от UIButton.

//
//  CustomButton1.swift
//

import UIKit

class CustomButton1: UIButton {
}


Далее в Main.storyboard добавляем кнопку в контроллер, присваиваем ей класс CustomButton1. Изменяем тип кнопки на Custom, а цвет Background на оранжевый.

image

Настройка кнопки


Кнопка будет иметь закруглённые края. Анимация будет осуществляться путём изменения альфа‑канала заднего плана с 0.3 до 1. Альфа‑канал названия кнопки при этом должен оставаться неизменным, поэтому для заднего цвета будет использоваться layer.backgroundColor, а не backgroundColor, который мы задали ранее.

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

class CustomButton1: UIButton {

    var color: UIColor = .black
    let touchDownAlpha: CGFloat = 0.3

    func setup() {
        backgroundColor = .clear
        layer.backgroundColor = color.cgColor

        layer.cornerRadius = 6
        clipsToBounds = true
    }
}


Автовызов настройки кнопки


Для того, чтобы настройки к кнопке из Storyboard применились автоматически, нужно добавить setup в метод awakeFromNib, не забыв при этом сохранить значение backgroundColor перед тем, как его обесцветить.

override func awakeFromNib() {
    super.awakeFromNib()

    if let backgroundColor = backgroundColor {
        color = backgroundColor
    }

    setup()
}


Программное создание кнопки


Не всегда удобно использовать Storyboard для создания кнопки, иногда лучше это сделать из программы. Для этого пропишем вспомогательный инициализатор.

convenience init(color: UIColor? = nil, title: String? = nil) {
    self.init(type: .custom)

    if let color = color {
        self.color = color
    }

    if let title = title {
        setTitle(title, for: .normal)
    }

    setup()        
}


Теперь, чтобы создать такую же кнопку внутри программы, достаточно написать так.

let button = CustomButton1(color: .orange, title: "Button")


Настройка кнопки готова.

События нажатия и отпускания кнопки


Перед тем, как перейти к анимации, нужно как-то ловить события нажатия и отпускания кнопки. Это можно сделать через поле isHighlighted. Методы touchDown и touchUp будут описаны ниже, пока они пустые.

override var isHighlighted: Bool {
    didSet {
        if isHighlighted {
            touchDown()
        } else {
            cancelTracking(with: nil)
            touchUp()
        }
    }
}

func touchDown() {
}

func touchUp() {
}


Анимация


Самое интересное! Как же реализовать анимацию? Первым делом напрашивается UIView.animate, но здесь есть небольшая загвоздка. При нажатии кнопки альфа‑канал заднего плана устанавливается в 0.3, а при отпускании плавно переходит в 1. Если в момент анимации пользователь снова нажал на кнопку, то анимация должна прерываться и значение альфа‑канала снова должно быть 0.3. Напоминаю, что кнопка должна работать как в последней iOS, так и в iOS8. Я не нашел простого варианта, как можно прервать UIView.animate в iOS8, поэтому использовал для анимации простой таймер.

weak var timer: Timer?

func stopTimer() {
    timer?.invalidate()
}

deinit {
    stopTimer()
}


Заполнение метода touchDown. Нужно остановить анимацию, если она происходит, и установить альфа‑канал заднего цвета в 0.3.

func touchDown() {
    stopTimer()

    layer.backgroundColor = color.withAlphaComponent(touchDownAlpha).cgColor
}


Заполнение метода touchUp. При отпускании кнопки нужно запустить анимацию, то есть сделать циклический таймер, который бы постепенно увеличивал альфа‑канал заднего плана. Но какой шаг для таймера выбрать? Опытным путём я установил, что шаг в 10 миллисекунд одинаково хорошо работает на всех моделях iPhone/iPad, даже на старых — iPhone 4s/5. Если сделать шаг чаще, то на 32‑битных контроллерах смотрится уже плохо.

Также опытным путём я установил, что стандартная анимация кнопок в iOS длится примерно 400 миллисекунд. Остаётся только вычислить альфа‑шаг, который стоит прибавлять на каждом шаге таймера и написать метод, который бы осуществлял это прибавление и следил за окончанием анимации.

let timerStep: TimeInterval = 0.01
let animateTime: TimeInterval = 0.4
lazy var alphaStep: CGFloat = {
    return (1 - touchDownAlpha) / CGFloat(animateTime / timerStep)
}()

func touchUp() {
    timer = Timer.scheduledTimer(timeInterval: timerStep,
                                 target: self,
                                 selector: #selector(animation),
                                 userInfo: nil,
                                 repeats: true)
}

@objc func animation() {
    guard let backgroundAlpha = layer.backgroundColor?.alpha else {
        stopTimer()

        return
    }

    let newAlpha = backgroundAlpha + alphaStep

    if newAlpha < 1 {
        layer.backgroundColor = color.withAlphaComponent(newAlpha).cgColor
    } else {
        layer.backgroundColor = color.cgColor

        stopTimer()
    }
}


Вот собственно и всё. Кнопка готова. Спасибо за внимание!

→ Проект на GitHub

© Habrahabr.ru