Анимирование скучных TableViews в вашем приложении iOS

Перевод статьи от автора Shubham Singh

Порадуйте своих пользователей привлекательной и увлекательной анимацией Таблицы

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

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

Мы будем анимировать ячейки табличного представления с помощью метода UIView.animate класса UIView. Вот описание анимированного метода —

Анимируйте изменения в одном или нескольких представлениях, используя указанную продолжительность. Во время анимации взаимодействие с пользователем временно отключается для анимированных представлений.

Раньше мне не хотелось пробовать анимацию UIView, я извинялся перед собой, что это было слишком сложно, в этом не было необходимости, на обучение уйдет много времени. Только когда я прочитал несколько блестящих блогов, книги по UI / UX и осознал силу анимации, я убедил себя и просто начал пробовать анимацию.

Хорошо, мы добавим четыре типа анимации, вот как будет выглядеть анимация ячеек TableView:

табличная анимация

. . .

Предпосылки

У вас должны быть некоторые знания о разработке приложений для iOS, даже если вы не слишком знакомы с концепциями, не стесняйтесь следовать за ними, я все объяснил просто. Все, что вам нужно, это MacBook, Xcode и некоторые знания AutoLayouts, метода UIView.animate и перечислений.

. . .

Вступление

Вступление в представление UI

Небольшое введение в представление, которое мы будем создавать в этом руководстве —

Представление будет иметь эти компоненты —

  • Таблица
  • Горизонтальный стэквью с 4 кнопками с одинаковым интервалом

С помощью этих 4 кнопок мы изменим анимацию ячеек TableViewHeader и TableView.

. . .

Руководство

Начните с создания одностраничного приложения в Xcode и выберите Storyboards в качестве пользовательского интерфейса.

Дизайн пользовательского интерфейса

Откройте Main.storyboard и выполните настройку представлений.

добавьте компоненты во ViewController в Main.Storyboard

Вот список того, что мы добавили —

  • Таблица — имеет ограничение сверху, справа, слева установленное в 0 и пропорции высоты 77% от представления.
  • Горизонтальный Стэквью — имеет верхний, левый, правый ограничения установленные на 24.
  • Кнопки — имеют ограничение по высоте 42, четыре из них уложены внутри stackView.

У каждой кнопки есть связанный с ней тег, добавьте их из атрибут инспектора.

  • Button1 имеет тег, равный 1.
  • Button2 имеет тег, равный 2.
  • Button3 имеет тег, равный 3.
  • Button4 имеет тег, равный 4.

Затем мы также создадим собственный TableViewClass со своим собственным XIB

  • Создайте новый класс CocoaTouch, установите подкласс как UITableViewCell и отметьте создание файла XIB.
  • Откройте XIB файл класса UITableViewCell.
кастомный UITableView XIB

Для этой ячейки мы добавили один containerView внутри представления содержимого, для которого верхние и нижние ограничения установлены на 5, а левое и правое ограничения установлены на 12.

. . .

Напишем код

Первое, мы добавим кода в класс UITableViewCell

import UIKit

class TableAnimationViewCell: UITableViewCell {
    override class func description() -> String {
        return "TableAnimationViewCell"
    }
    
    // MARK:- outlets for the viewController
    @IBOutlet weak var containerView: UIView!
    
    // properties for the tableViewCell
    var tableViewHeight: CGFloat = 62
    var color = UIColor.white {
        didSet {
            self.containerView.backgroundColor = color
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.selectionStyle = .none
        self.containerView.layer.cornerRadius = 4
    }
}

Класс UITableViewCell очень простой

Мы добавили аутлет для containerView и установили две переменные —

  • TableViewHeight — определяет высоту ячейки при доступе из viewController.
  • Color — определяет цвет ячейки.

Мы также добавили код, чтобы установить для стиля выделения значение «Нет» и скруглить углы ячейки.

Затем, мы создали класс TableViewAnimator, который мы добавим в наш класс анимации таблицы.

Я рекомендую вам всегда создавать отдельный класс для анимации, чтобы он был универсальным и мог применяться к любым представлениям. Есть еще одно преимущество в создании настраиваемого класса: он остается легко настраиваемым, а желаемая продолжительность и эффект могут быть предоставлены в инициализаторе.

Давайте создадим класс TableViewAnimator

import UIKit

// defining a typealias for ease of use
typealias TableCellAnimation = (UITableViewCell, IndexPath, UITableView) -> Void

// class to animate the tableViews with the presented animation
final class TableViewAnimator {
    private let animation: TableCellAnimation
    
    init(animation: @escaping TableCellAnimation) {
        self.animation = animation
    }
    
    func animate(cell: UITableViewCell, at indexPath: IndexPath, in tableView: UITableView) {
        animation(cell, indexPath, tableView)
    }
}

Здесь мы создали псевдоним типа и определили класс для TableViewAnimator.

Затем, давайте создадим перечисление TableAnimationFactory, и добавим 4 метода анимации —

///enums providing tableViewCell animations
enum TableAnimationFactory {
    
    /// fades the cell by setting alpha as zero and then animates the cell's alpha based on indexPaths
    static func makeFadeAnimation(duration: TimeInterval, delayFactor: TimeInterval) -> TableCellAnimation {
        return { cell, indexPath, _ in
            cell.alpha = 0
            UIView.animate(
                withDuration: duration,
                delay: delayFactor * Double(indexPath.row),
                animations: {
                    cell.alpha = 1
            })
        }
    }
    
    /// fades the cell by setting alpha as zero and moves the cell downwards, then animates the cell's alpha and returns it to it's original position based on indexPaths
    static func makeMoveUpWithFadeAnimation(rowHeight: CGFloat, duration: TimeInterval, delayFactor: TimeInterval) -> TableCellAnimation {
        return { cell, indexPath, _ in
            cell.transform = CGAffineTransform(translationX: 0, y: rowHeight * 1.4)
            cell.alpha = 0
            UIView.animate(
                withDuration: duration,
                delay: delayFactor * Double(indexPath.row),
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
                    cell.alpha = 1
            })
        }
    }

    /// moves the cell downwards, then animates the cell's by returning them to their original position based on indexPaths
    static func makeMoveUpAnimation(rowHeight: CGFloat, duration: TimeInterval, delayFactor: TimeInterval) -> TableCellAnimation {
        return { cell, indexPath, _ in
            cell.transform = CGAffineTransform(translationX: 0, y: rowHeight * 1.4)
            UIView.animate(
                withDuration: duration,
                delay: delayFactor * Double(indexPath.row),
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
            })
        }
    }
    
    /// moves the cell downwards, then animates the cell's by returning them to their original position with spring bounce based on indexPaths
    static func makeMoveUpBounceAnimation(rowHeight: CGFloat, duration: TimeInterval, delayFactor: Double) -> TableCellAnimation {
        return { cell, indexPath, tableView in
            cell.transform = CGAffineTransform(translationX: 0, y: rowHeight)
            UIView.animate(
                withDuration: duration,
                delay: delayFactor * Double(indexPath.row),
                usingSpringWithDamping: 0.6,
                initialSpringVelocity: 0.1,
                options: [.curveEaseInOut],
                animations: {
                    cell.transform = CGAffineTransform(translationX: 0, y: 0)
            })
        }
    }
}

Мы добавили четыре типа анимации сюда, в перечисление, перечисление служит фабрикой анимации, которая предоставляет анимацию классу аниматора. Четыре анимации —

  1. Плавная анимация: Анимирует ячейки TableView на основе альфа ячейки.
  2. Анимация движения вверх: Анимирует ячейки TableView в зависимости от положения ячейки.
  3. Анимация движения-вверх-затухание: Анимирует ячейки TableView в зависимости от положения и альфа-канала ячейки.
  4. Анимация движения-вверх-отскока: Анимирует ячейки TableView в зависимости от положения ячейки с помощью пружинной анимации.

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

Перечисления отлично подходят для сопоставления функций с случаями, они гарантируют, что код всегда без ошибок.

Давайте создадим файл Tables.swift и создадим перечисление TableAnimation для обеспечения данными ViewController

import UIKit

/// Enum provider for providing the animationTitle and an animation method from the factory
enum TableAnimation {
    case fadeIn(duration: TimeInterval, delay: TimeInterval)
    case moveUp(rowHeight: CGFloat, duration: TimeInterval, delay: TimeInterval)
    case moveUpWithFade(rowHeight: CGFloat, duration: TimeInterval, delay: TimeInterval)
    case moveUpBounce(rowHeight: CGFloat, duration: TimeInterval, delay: TimeInterval)
    
    // provides an animation with duration and delay associated with the case
    func getAnimation() -> TableCellAnimation {
        switch self {
        case .fadeIn(let duration, let delay):
            return TableAnimationFactory.makeFadeAnimation(duration: duration, delayFactor: delay)
        case .moveUp(let rowHeight, let duration, let delay):
            return TableAnimationFactory.makeMoveUpAnimation(rowHeight: rowHeight, duration: duration, 
                                                             delayFactor: delay)
        case .moveUpWithFade(let rowHeight, let duration, let delay):
            return TableAnimationFactory.makeMoveUpWithFadeAnimation(rowHeight: rowHeight, duration: duration,
                                                                     delayFactor: delay)
        case .moveUpBounce(let rowHeight, let duration, let delay):
            return TableAnimationFactory.makeMoveUpBounceAnimation(rowHeight: rowHeight, duration: duration,
                                                                   delayFactor: delay)
        }
    }
    
    // provides the title based on the case
    func getTitle() -> String {
        switch self {
        case .fadeIn(_, _):
            return "Fade-In Animation"
        case .moveUp(_, _, _):
            return "Move-Up Animation"
        case .moveUpWithFade(_, _, _):
            return "Move-Up-Fade Animation"
        case .moveUpBounce(_, _, _):
            return "Move-Up-Bounce Animation"
        }
    }
}

Мы определили перечисление TableAnimation с четырьмя случаями для четырех анимаций вместе со связанными значениями, которое предоставляется методам Animation Factory.

Перечисление также имеет две функции —

  • GetAnimation возвращает анимацию из animationFactory на основе случаев перечисления и связанных с ней значений.
  • GetTitle возвращает заголовок анимации на основе случаев перечисления.

Давайте напишем код для ViewController

Начнем с соединения аутлетов представления во ViewController — (У нас так же есть аутлет функции кнопки который мы определим позже)

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // MARK:- outlets for the viewController
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var button3: UIButton!
    @IBOutlet weak var button4: UIButton!

Добавьте переменные для ViewController

// MARK:- variables for the viewController
var colors = [UIColor.systemRed, UIColor.systemBlue, UIColor.systemOrange,
              UIColor.systemPurple,UIColor.systemGreen]
var tableViewHeaderText = ""

/// an enum of type TableAnimation - determines the animation to be applied to the tableViewCells
var currentTableAnimation: TableAnimation = .fadeIn(duration: 0.85, delay: 0.03) {
    didSet {
        self.tableViewHeaderText = currentTableAnimation.getTitle()
    }
}
var animationDuration: TimeInterval = 0.85
var delay: TimeInterval = 0.05
var fontSize: CGFloat = 26

Здесь краткое описание для каждой переменной —

  • Colors — цвета, которые мы представим в TableView.
  • TableViewHeaderText — определяет заголовок заголовка TableView.
  • CurrentTableAnimation — это тип значения модели перечисления, который мы определили ранее, он устанавливает переменную TableViewHeaderText при изменении самого перечисления.
  • AnimationDuration — определяет продолжительность анимации ячейки.
  • Delay  — определяет задержку между анимацией каждой ячейки.
  • FrontSize — определяет размер символов для кнопок.

Далее, зарегистрируйте TableView и установите метод жизненого цикла ViewController

Мы зарегистрировали пользовательский класс UITableViewCell с нашим TableView и установили delegate и dataSource для viewController, мы установили символ button1, чтобы он отображался как выбранный, а также мы асинхронно перезагружаем данные для отображения анимации.

Затем, давайте установим методы delegate и dataSource TableView во ViewController

// MARK:- lifecycle methods for the ViewController
override func viewDidLoad() {
    super.viewDidLoad()
    self.colors.append(contentsOf: colors.shuffled())
    
    // registering the tableView
    self.tableView.register(UINib(nibName: TableAnimationViewCell.description(), bundle: nil),
                            forCellReuseIdentifier: TableAnimationViewCell.description())
    self.tableView.delegate = self
    self.tableView.dataSource = self
    self.tableView.isHidden = true
    
    // set the separatorStyle to none and set the Title for the tableView
    self.tableView.separatorStyle = .none
    self.tableViewHeaderText = self.currentTableAnimation.getTitle()
    
    // set the button1 as selected and reload the data of the tableView to see the animation
    button1.setImage(UIImage(systemName: "1.circle.fill", withConfiguration:
                                UIImage.SymbolConfiguration(pointSize: fontSize, weight: .semibold, scale: .large)), for: .normal)
    DispatchQueue.main.asyncAfter(deadline: .now()) {
        self.tableView.isHidden = false
        self.tableView.reloadData()
    }
}

Мы добавили стандартные методы для инициализации TableView, давайте вкратце рассмотрим их —

  • numberOfRowsInSection определяет количество отображаемых ячеек TableView.
  • heightForRowAt определяет высоту ячеек TableView
  • cellForRowAt инициализирует ячейку для indexPath, назначает ей цвет и возвращает ячейку в TableView.
  • viewForHeaderInSection определяет представление для TableHeader, мы настроили его для отображения метки со значением, полученным из перечисления.
  • heightForHeaderInSection определяет высоту заголовка таблицы.
  • willDisplay является наиболее важным методом в этом руководстве, мы получаем анимацию из перечисления currentAnimation, инициализируем класс TableViewAnimator этой анимацией, а затем анимируем ячейку, вызывая метод animate аниматора.

Наконец, давайте добавим код для взаимодействия кнопок, каждая кнопка должна перезагружать таблицу со значением, полученным из созданного нами перечисления Table

 // delegate functions for the tableView
 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return colors.count
 }
 
 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return TableAnimationViewCell().tableViewHeight
 }
 
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: TableAnimationViewCell.description(),
                                                for: indexPath) as? TableAnimationViewCell {
        // set the color of the cell
        cell.color = colors[indexPath.row]
        return cell
    }
    fatalError()
 }
 
 // for displaying the headerTitle for the tableView
 func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerView = UIView(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 42))
    headerView.backgroundColor = UIColor.systemBackground
    
    let label = UILabel()
    label.frame = CGRect(x: 24, y: 12, width: self.view.frame.width, height: 42)
    label.text = tableViewHeaderText
    label.textColor = UIColor.label
    label.font = UIFont.systemFont(ofSize: 26, weight: .medium)
    headerView.addSubview(label)
    return headerView
 }
 
 func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 72
 }
 
 //
 func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // fetch the animation from the TableAnimation enum and initialze the TableViewAnimator class
    let animation = currentTableAnimation.getAnimation()
    let animator = TableViewAnimator(animation: animation)
    animator.animate(cell: cell, at: indexPath, in: tableView)
 }

Примечание. Все четыре кнопки должны быть назначены одной функции выхода, в этом руководстве я назначил ее функции animationButtonPressed.

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

Теперь, на основе тегов, назначенных кнопке, случай переключателя помогает установить символ этой конкретной кнопки как заполненный, и, аналогично, переменная currentTableAnimation также устанавливается соответствующим образом —

  • Button1 применяет анимацию постепенного появления к TableViewCells.
  • Button2 применяет анимацию перемещения вверх к TableViewCell.
  • Button3 применяет анимацию Move-Up-Fade к TableViewCells.
  • Button4 применяет анимацию Move-Up-Bounce к TableViewCells.

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

Вот конечный результат, анимируйте TableView, нажимая кнопки —

Законченная анимация таблицы

. . .

Ресурсы

  1. Github репозиторий
  2. Документация Apple по анимации

. . .

Заключение

Давайте повторим что мы выучили сегодня.

Мы начали с добавления компонентов, необходимых для анимации TableView, в файл Main.Storyboard.

Затем, создали настраиваемый класс UITableViewClass и добавили компоненты в XIB файл.

Затем, мы создали TableViewAnimator класс, который анимирует TableView, помните, о создании отдельного класса для каждой анимации. Мы также создали перечисление TableAnimationFactory, где определили 4 анимации.

Затем, мы создали перечисление TableAnimation, чтобы сгруппировать анимацию и заголовок для TableViews в отдельных случаях путем создания функций.

Наконец, мы написали код для ViewController, мы настроили TableView, мы добавили функцию выхода для кнопок и назначили ей анимацию для установки анимации TableView.

Спасибо, что прочитали эту статью. Надеюсь увидеть вас снова в следующей статье!