UICollectionViewDiffableDataSource 正在替换数据而不是更新
UICollectionViewDiffableDataSource is replacing data instead of updating
我正在努力理解 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
}
}
您每次都在为您的 apply(_ photos: [Photo])
方法提供新的数据集。您需要为其提供更新的数据集,以便差异逻辑可以应用更改。
现在你只是每次都给它新的数据。
在数组中捕获您的 Photos
并使用 didSet
观察者
调用 apply
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)
}
}
}
不使用额外数组初始化 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)
}
不需要属性
私有 var 快照 = DataSourceSnapshot()
注:
尽可能具体地声明数据源
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
})
我正在努力理解 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
}
}
您每次都在为您的 apply(_ photos: [Photo])
方法提供新的数据集。您需要为其提供更新的数据集,以便差异逻辑可以应用更改。
现在你只是每次都给它新的数据。
在数组中捕获您的 Photos
并使用 didSet
观察者
apply
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)
}
}
}
不使用额外数组初始化 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
})