Программирование
Современные способы перезагрузки ячеек таблицы и коллекции на 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)
}
Приведенный выше код довольно прост.
- Получите выбранный объект Superhero (selectedHero) из источника данных, используя index path.
- Добавьте «★» к имени выбранного Героя.
- Сделайте копию текущего снепшота diffable data source, чтобы мы могли изменить его позже.
- Измените новую копию снепшота diffable data source, перезагрузив в нее selectedHero.
- Примените снимок к 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.
Спасибо за прочтение!
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2024.51
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2024.52
-
Видео и подкасты для разработчиков4 недели назад
Как ВКонтакте измеряет перфоманс приложений
-
Видео и подкасты для разработчиков4 недели назад
Compose и SwiftUI: найди 10 отличий