Connect with us

Программирование

Современные способы перезагрузки ячеек таблицы и коллекции на Swift

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

Опубликовано

/

     
     

В iOS 13 Apple представила diffable data source и snapshot, создав современную эру представления таблицы и коллекций. До этого перезагрузку ячейки таблицы или представления коллекции можно было выполнить, вызвав одну из следующих функций:

reloadRows(at:with:) // For reloading table view cell
reloadItems(at:) // For reloading collection view cell

Для представлений таблиц и коллекций, построенных с использованием diffable data source, это уже неверно. Если это так, то как разработчики должны перезагружать свои таблицы и ячейки коллекции?

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

Пример приложения

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

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

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

Перезагрузка элементов ссылочного типа

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

class Superhero: Hashable {

    var name: String

    init(name: String) {
        self.name = name
    }

    // MARK: Hashable
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }

    static func == (lhs: ReloadReferenceTypeViewController.Superhero,
                    rhs: ReloadReferenceTypeViewController.Superhero) -> Bool {
        lhs.name == rhs.name
    }
}

Как видно, класс Superhero — это простой класс с переменной с именем name.

Обратите внимание, что нам нужно явно реализовать функции hash (into :) и == (lhs: rhs :), потому что классы не поддерживают автоматическое соответствие Hashable.

Теперь, когда вы познакомились с классом Superhero, давайте перейдем к основной теме этой статьи — перезагрузке ячеек.

Мы выполним перезагрузку ячейки в методе делегата collectionView (_: didSelectItemAt :). Вот как мы это делаем:

func collectionView(_ collectionView: UICollectionView,
                    didSelectItemAt indexPath: IndexPath) {
    
    // 1
    // Get selected hero using index path
    guard let selectedHero = dataSource.itemIdentifier(for: indexPath) else {
        collectionView.deselectItem(at: indexPath, animated: true)
        return
    }
    
    // 2
    // Update selectedHero
    selectedHero.name = selectedHero.name.appending(" ★")

    // 3
    // Create a new copy of data source snapshot for modification
    var newSnapshot = dataSource.snapshot()
    
    // 4
    // Reload selectedHero in newSnapshot
    newSnapshot.reloadItems([selectedHero])
    
    // 5
    // Apply snapshot changes to data source
    dataSource.apply(newSnapshot)
}

Приведенный выше код довольно прост.

  1. Получите выбранный объект Superhero (selectedHero) из источника данных, используя index path.
  2. Добавьте «★» к имени выбранного Героя.
  3. Сделайте копию текущего снепшота diffable data source, чтобы мы могли изменить его позже.
  4. Измените новую копию снепшота diffable data source, перезагрузив в нее selectedHero.
  5. Примените снимок к diffable data source. В представлении коллекции будут отражены изменения снимка.

Один большой недостаток приведенного выше кода заключается в том, что он работает только с элементами ссылочного типа. Почему это так?

Чтобы понять, что происходит, вы должны сначала понять разницу между типом значения и ссылочным типом. Короче говоря, если Superhero является типом значения, selectedHero будет новым экземпляром Superhero, он не будет указывать на выбранный объект Superhero в снепшоте. Поэтому, если вы попытаетесь перезагрузить selectedHero в newSnapshot, вы получите исключение NSInternalInconsistencyException с причиной «Invalid item identifier specified for reload».

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

Перезагрузка элементов типа значение

На этом этапе вам может быть интересно, почему некоторые разработчики могут предпочесть использовать тип значения (структуру) в качестве типа идентификатора элемента вместо ссылочного типа (класса). Для этого есть разные причины, и наиболее важной причиной использования struct является то, что ее определение чище и проще.

struct Superhero: Hashable {
    var name: String
}

Как видите, мы значительно сократили объем кода в определении благодаря помощи автоматического согласования с Hashable и автоматического синтеза инициализаторов.

Возвращаясь к коду перезагрузки ячейки, он немного отличается от кода перезагрузки ячейки ссылочного типа. Как упоминалось ранее, элементы типа значения не будут работать с reloadItems (_ :). Если да, то что мы можем с этим поделать?

К счастью, мы можем легко обойти это, заменив выбранный объект Superhero (selectedHero) в снимке на новый объект Superhero (updatedHero).

func collectionView(_ collectionView: UICollectionView,
                  didSelectItemAt indexPath: IndexPath) {

  // Get selected hero using index path
  guard let selectedHero = dataSource.itemIdentifier(for: indexPath) else {
      collectionView.deselectItem(at: indexPath, animated: true)
      return
  }

  // Create a new copy of selectedHero & update it
  var updatedHero = selectedHero
  updatedHero.name = updatedHero.name.appending(" ★")

  // Create a new copy of data source snapshot for modification
  var newSnapshot = dataSource.snapshot()

  // Replacing selectedHero with updatedHero
  newSnapshot.insertItems([updatedHero], beforeItem: selectedHero)
  newSnapshot.deleteItems([selectedHero])

  // Apply snapshot changes to data source
  dataSource.apply(newSnapshot)
}

Обратите внимание, что приведенный выше код работает только с элементами типа значение, если вы примените приведенный выше код к элементам ссылочного типа, вы получите исключение NSInternalInconsistencyException с причиной «Invalid update: destination for insertion operation [struct_instance] is in the insertion identifier list for update».

Заключение

Честно говоря, я не уверен, почему Apple разработала API NSDiffableDataSourceSnapshot таким образом, что он не работает как с ссылочными типами, так и с типами значений.

Я подозреваю, что это может быть связано с некоторыми техническими ограничениями, о которых мы не знаем. Тем не менее, я надеюсь, что Apple улучшит API, предоставив нам стандартизированный способ перезагрузки ячеек таблицы и представления коллекции.

Скачайте полный пример кода на GitHub.

Спасибо за прочтение!

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Advertisement

Популярное

X

Спасибо!

Теперь редакторы в курсе.