UICollectionViewDiffableDataSource заменяет данные вместо обновления
Я пытаюсь понять UICollectionViewDiffableDataSource
а также NSDiffableDataSourceSnapshot
.
Ниже я создал очень грубую версию. По сути, при загрузке он должен загружать фотографии.
При нажатии кнопки на панели навигации происходит переход на следующую страницу. Однако это просто замена существующих данных, я ожидал, что они добавят значения в массив.
Как мне обновить свои данные, а не заменить их?
class ViewController: UIViewController {
lazy var collectionView = UICollectionView(frame: view.frame, collectionViewLayout: UICollectionViewFlowLayout())
enum Section {
case main
}
typealias DataSource = UICollectionViewDiffableDataSource<Section, AnyHashable>
typealias DataSourceSnapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>
private var dataSource: DataSource!
private var snapshot = DataSourceSnapshot()
override func viewDidLoad() {
super.viewDidLoad()
let updateButton = UIBarButtonItem(title: "Add", style: .plain, target: self, action: #selector(onTapLoad))
navigationItem.rightBarButtonItem = updateButton
view.addSubview(collectionView)
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.delegate = self
collectionView.backgroundColor = .systemBackground
collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: "PhotoCell")
dataSource = DataSource(collectionView: collectionView, cellProvider: { (cv, indexPath, object) -> PhotoCell? in
if let object = object as? Photo {
let cell = cv.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
cell.backgroundColor = indexPath.item % 2 == 0 ? .systemTeal : .systemPink
cell.label.text = object.title
return cell
}
return nil
})
load()
}
@objc func onTapLoad() {
load(page: 1)
}
func load(page: Int = 0) {
PhotoLoader.shared.load(page: page) { result in
if let photos = try? result.get() {
self.apply(photos)
}
}
}
func apply(_ photos: [Photo]) {
snapshot = DataSourceSnapshot()
snapshot.appendSections([Section.main])
snapshot.appendItems(photos)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionView.frame.width - 32, height: 100)
}
}
struct Photo: Decodable, Hashable {
let id = UUID()
let title: String
}
final class PhotoLoader {
static let shared = PhotoLoader()
func load(page: Int, completion: @escaping (Result<[Photo], Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "https://jsonplaceholder.typicode.com/photos?_start=\(page)&_limit=5")!, completionHandler: { data, response, error in
if let data = data, let model = try? JSONDecoder().decode([Photo].self, from: data) {
completion(.success(model))
}
}).resume()
}
}
final class PhotoCell: UICollectionViewCell {
lazy var label = UILabel(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 3
addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: topAnchor),
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
required init?(coder: NSCoder) {
return nil
}
}
2 ответа
Без использования дополнительного массива инициализируйте снимок вviewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
...
let snapshot = DataSourceSnapshot()
snapshot.appendSections([Section.main])
dataSource.apply(snapshot, animatingDifferences: false)
}
И в apply(_ photos)
добавлять фотографии к текущему снимку, а не создавать новый
func apply(_ photos: [Photo]) {
var snapshot = dataSource.snapshot()
snapshot.appendItems(photos, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)
}
Свойство не нужно
Примечание:
Объявите источник данных как можно более конкретным
typealias DataSource = UICollectionViewDiffableDataSource<Section, Photo>
typealias DataSourceSnapshot = NSDiffableDataSourceSnapshot<Section, Photo>
Тогда вы избавитесь от ненужной проверки типа в
dataSource = DataSource(collectionView: collectionView, cellProvider: { (cv, indexPath, photo) -> PhotoCell? in
let cell = cv.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
cell.backgroundColor = indexPath.item % 2 == 0 ? .systemTeal : .systemPink
cell.label.text = photo.title
return cell
})
Вы предоставляете свой apply(_ photos: [Photo])
метод с новым набором данных каждый раз. Вам необходимо предоставить ему обновленный набор данных, чтобы логика сравнения могла применить изменения.
Прямо сейчас вы просто каждый раз даете ему новые данные.
Захватите свой Photos
в массиве и вызовите apply
с использованием didSet
наблюдатель
private var photos: [Photo] = [] {
didSet { apply(photos) }
}
.....
func load(page: Int = 0) {
PhotoLoader.shared.load(page: page) { result in
if let photos = try? result.get() {
self.photos.append(contentsOf: photos)
}
}
}