Туториал: Настройка анимации вставляемых ячеек UICollectionView

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

Xcode: 
Xcode 8
Swift: 
Swift 3

UICollectionView всегда был мощным инструментом. Многофункциональность и универсальность этого класса трудно не оценить.

Сегодня мы более подробно рассмотрим настройку анимации новых добавленных collection view cell. Давайте начнем!

Начнем мы с создания нового single view app в Xcode.

Перетаскиваем UICollectionViewController  на наш холст в Interface Builder, устанавливаем его класс как класс ViewController ( идет как шаблон Xcode), затем выделяем ячейку и в Attribute Inspector устанавливаем ее идентификатор как "ItemCell".  Обновим ViewController.swift следующим кодом:

extension UIColor {
    static func random() -> UIColor{
        return UIColor(
            red: CGFloat(drand48()),
            green: CGFloat(drand48()),
            blue: CGFloat(drand48()),
            alpha: 1.0
        )
    }
}

struct Item {
    var color: UIColor
}

class ViewController: UICollectionViewController {
  var items = [Item]()

  func addItem() { items.append(Item(color: .random())) }

  override func viewDidLoad() {
    super.viewDidLoad()
    for _ in 0...10 { addItem() }
  }

  override func collectionView( _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int {
    return items.count
  }

  override func collectionView( _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath ) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: "ItemCell", 
      for: indexPath
    )

    cell.contentView
      .backgroundColor = items[indexPath.item].color

    return cell
  }  
}

Затем мы вставим его в UINavigationController и добавим UIBarButtonItem для того, чтобы можно было добавлять новые элементы.

Через сочетание клавиш Control+Drag в ViewController.swift добавим новый экшен. Связываем его следующим образом:

@IBAction func add(_ sender: UIBarButtonItem) {
  addItem()

  let indexPath = IndexPath( item: self.items.count - 1, section: 0 )

  collectionView?.performBatchUpdates({ self.collectionView?.insertItems(at: [indexPath]) }, completion: nil)
}

Итак у нас получился вполне себе «шаблонный вариант». Только квадратные, случайно окрашенные, постепенно появляющиеся при добавлении ячейки:

Неплохо. Теперь давайте настроим поведение этих ячеек при их добавлении. Для этого нам понадобится собственный пользовательский подкласс UICollectionViewLayout.

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

class CustomFlowLayout : UICollectionViewFlowLayout {
  var insertingIndexPaths = [IndexPath]()
}

Мы добавили свойство, чтобы отслеживать добавляемые index path во время каждого пакетного обновления collection view.

Чтобы заполнить наше свойство, нам нужно переопеределить несколько функций:

override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
  super.prepare(forCollectionViewUpdates: updateItems)

  insertingIndexPaths.removeAll()

  for update in updateItems {
    if let indexPath = update.indexPathAfterUpdate, update.updateAction == .insert {
      insertingIndexPaths.append(indexPath)
    }
  }
}

override func finalizeCollectionViewUpdates() {
  super.finalizeCollectionViewUpdates()

  insertingIndexPaths.removeAll()
}

Отлично. Ничего особенного здесь мы не ждем, просто собираем добавленные (вставленные) index path в начале каждого обновления, а затем очищаем (удаляем) их в конце.

Настоящая магия произойдет тогда, когда мы переопределим последнюю функцию:

override func initialLayoutAttributesForAppearingItem(
  at itemIndexPath: IndexPath
) -> UICollectionViewLayoutAttributes? {
  let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)

  if insertingIndexPaths.contains(itemIndexPath) {
    attributes?.alpha = 0.0
    attributes?.transform = CGAffineTransform(
      scaleX: 0.1, 
      y: 0.1
    )
  }

  return attributes
}

Здесь мы взяли свойство initialLayoutAttributesForAppearingItem, которое собирался использовать наш collection view для вновь вставленного элемента, и немного изменили его, прежде чем передать обратно в систему.

Мы добавили свойство transform, чтобы уменьшить размер элемента, когда его только добавили. Мы получаем хороший эффект «зума» при добавлении каждого элемента. Теперь добавьте строку в ViewController.swift в начало метода ViewDidLoad:

collectionView?.setCollectionViewLayout(CustomFlowLayout(), animated: false)

Вот что должно получиться:

Это уже выглядит неплохо, но давайте попробуем что-то еще более изящное. Мы можем изменить наш transform на:

attributes?.transform = CGAffineTransform(
  translationX: 0, 
  y: 500.0
)

Одним таким изменением мы можем добиться совершенно иного эффекта:

Класс!

А что дальше?

Конечный проект вы можете скачать тут.

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

Урок подготовил: Иван Акулов

Источник урока.