我很难找到NSDiffableDataSourceSnapshot reloadItems(_:)的用法
您可能会认为第二点的答案是:好吧,项目标识符对象的其他方面可能不是其等式的一部分,而是反映到了单元格接口中。但是我发现这不是真的;在调用reloadItems之后,表视图没有反映更改。
因此,当我想要更改一个项目时,我对快照所做的最后是在要替换的项之后的insert,然后是原始项的delete。没有快照replace方法,这正是我所希望的reloadItems会变成的方法。
(我对这些术语做了一次堆栈溢出搜索,发现很少--主要是几个对reloadItems的特殊用法(如如何使用可扩展的UITableView更新表单元格 )感到困惑的问题。所以我要问的是,这个方法有什么实际用途?)
好吧,没有什么比有一个最小的可重复的例子来玩,所以这里有一个。
使用模板iOS创建一个普通的普通ViewController项目,并将此代码添加到ViewController中。
我一片一片地拿着。首先,我们有一个作为项目标识符的结构。UUID是唯一的部分,所以均匀度和可操纵性仅取决于它:
struct UniBool : Hashable {
let uuid : UUID
var bool : Bool
// equatability and hashability agree, only the UUID matters
func hash(into hasher: inout Hasher) {
hasher.combine(uuid)
}
static func ==(lhs:Self, rhs:Self) -> Bool {
lhs.uuid == rhs.uuid
}
}接下来,(假)表视图和不同的数据源:
let tableView = UITableView(frame: .zero, style: .plain)
var datasource : UITableViewDiffableDataSource<String,UniBool>!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.datasource = UITableViewDiffableDataSource<String,UniBool>(tableView: self.tableView) { tv, ip, isOn in
let cell = tv.dequeueReusableCell(withIdentifier: "cell", for: ip)
return cell
}
var snap = NSDiffableDataSourceSnapshot<String,UniBool>()
snap.appendSections(["Dummy"])
snap.appendItems([UniBool(uuid: UUID(), bool: true)])
self.datasource.apply(snap, animatingDifferences: false)
}因此,在我们的不同数据源中只有一个UniBool,它的bool是true。因此,现在设置一个按钮来调用此操作方法,该方法试图使用bool切换reloadItems值。
@IBAction func testReload() {
if let unibool = self.datasource.itemIdentifier(for: IndexPath(row: 0, section: 0)) {
var snap = self.datasource.snapshot()
var unibool = unibool
unibool.bool = !unibool.bool
snap.reloadItems([unibool]) // this is the key line I'm trying to test!
print("this object's isOn is", unibool.bool)
print("but looking right at the snapshot, isOn is", snap.itemIdentifiers[0].bool)
delay(0.3) {
self.datasource.apply(snap, animatingDifferences: false)
}
}
}事情是这样的。我对reloadItems说了一个条目,它的UUID是匹配的,但是它的bool被切换:“这个对象的isON是假的”。但当我问快照,好吧,你有什么?它告诉我,它的唯一项标识符的bool仍然是真的。
这就是我要问的。如果快照不会获取bool的新值,那么reloadItems首先用于什么?
显然,我只需替换一个不同的UniBool,即一个具有不同UUID的。但是我不能调用reloadItems;我们崩溃了,因为数据中还没有那个UniBool。我可以通过调用insert和remove来解决这个问题,而这正是我所做的工作。
但我的问题是:如果不是为了这件事,那么reloadItems是为了什么呢?
发布于 2020-10-01 23:41:46
(我对问题中所展示的行为提出了错误意见,因为我不认为这是好行为。但是,就目前的情况而言,我想我可以猜测一下这个想法的意图。)
当您将快照告知reload某一项时,它不会读取您提供的项目的数据!它只是简单地查看项,作为识别数据源中已经存在的项的一种方式,您正在请求重新加载。
(因此,如果您提供的项与数据源中已经提供的项相等但不完全相同,则您所提供的项与数据源中已经存在的项之间的“差异”将与无关;数据源将永远不会被告知任何不同之处。)
然后,当您将快照apply到数据源时,数据源将通知表视图重新加载相应的单元格。这将导致再次调用数据源的单元格提供程序函数。
好的,因此调用数据源的单元提供程序函数,通常有三个参数:表视图、索引路径和数据源中的数据。但是我们刚刚说过,来自数据源的数据并没有改变。那么,重新装载有什么意义呢?
显然,答案是,预计单元提供程序函数将在其他地方查找,以获得(至少部分)新数据,以显示在新退出队列的单元中。您需要有某种类型的“后备存储”,以供单元提供程序查看。例如,您可能正在维护一个字典,其中键是单元标识符类型,值是可能重新加载的额外信息。
这必须是合法的,因为根据定义,单元标识符类型是可选的,因此可以用作字典键,而且单元格标识符必须在数据中是唯一的,否则数据源将拒绝数据(通过崩溃)。查找将是即时的,因为这是一本字典。
下面是一个完整的工作示例,您只需将其复制并粘贴到项目中即可。该表描绘了三个名字,以及一个星星,用户可以点击使星星被填充或空,表示最喜欢或不喜欢。名称存储在不同的数据源中,但最喜欢的状态存储在外部备份存储中。
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
class TableViewController: UITableViewController {
var backingStore = [String:Bool]()
var datasource : UITableViewDiffableDataSource<String,String>!
override func viewDidLoad() {
super.viewDidLoad()
let cellID = "cell"
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
self.datasource = UITableViewDiffableDataSource<String,String>(tableView:self.tableView) {
tableView, indexPath, name in
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
var config = cell.defaultContentConfiguration()
config.text = name
cell.contentConfiguration = config
var accImageView = cell.accessoryView as? UIImageView
if accImageView == nil {
let iv = UIImageView()
iv.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(self.starTapped))
iv.addGestureRecognizer(tap)
cell.accessoryView = iv
accImageView = iv
}
let starred = self.backingStore[name, default:false]
accImageView?.image = UIImage(systemName: starred ? "star.fill" : "star")
accImageView?.sizeToFit()
return cell
}
var snap = NSDiffableDataSourceSnapshot<String,String>()
snap.appendSections(["Dummy"])
let names = ["Manny", "Moe", "Jack"]
snap.appendItems(names)
self.datasource.apply(snap, animatingDifferences: false)
names.forEach {
self.backingStore[$0] = false
}
}
@objc func starTapped(_ gr:UIGestureRecognizer) {
guard let cell = gr.view?.next(ofType: UITableViewCell.self) else {return}
guard let ip = self.tableView.indexPath(for: cell) else {return}
guard let name = self.datasource.itemIdentifier(for: ip) else {return}
guard let isFavorite = self.backingStore[name] else {return}
self.backingStore[name] = !isFavorite
var snap = self.datasource.snapshot()
snap.reloadItems([name])
self.datasource.apply(snap, animatingDifferences: false)
}
}发布于 2020-09-27 21:22:16
基于您的新示例代码,我同意,它看起来像一个bug。将reloadItems添加到快照时,它将正确地触发数据源闭包以请求更新的单元格,但传递给闭包的IdentifierType项是原始的,而不是reloadItems调用提供的新值。
如果我将您的UniBool结构更改为类,使其成为引用而不是值类型,则一切按预期进行(因为现在只有一个UniBool实例,而不是一个具有相同标识符的新实例)。
目前似乎有几个可能的工作--围绕:
IdentifierType使用引用而不是值类型indexPath访问它。我不认为这两种都是理想的。
有趣的是,在将UniBool更改为类之后,我尝试创建一个与现有实例具有相同uuid的新UniBool实例,并重新加载该实例;代码崩溃时会出现一个异常,说明指定用于重新加载的无效项标识符;这听起来不正确;只有hashValue应该重要,而不是实际的对象引用。原始对象和新对象都具有相同的hashValue,==返回true。
原始答案
reloadItems可以工作,但是有两个要点:
snapshot开始,并对此调用reloadItems。不能创建新快照。item之外,您不能依赖传递给CellProvider闭包的CellProvider--它并不代表支持模型(数组)中的最新数据。第2点意味着您需要使用提供的indexPath或item.id从模型中获取更新的对象。
我创建了一个简单的示例,它在表行中显示当前时间;这是数据源结构:
struct RowData: Hashable {
var id: UUID = UUID()
var name: String
private let possibleColors: [UIColor] = [.yellow,.orange,.cyan]
var timeStamp = Date()
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
static func ==(lhs: RowData, rhs: RowData) -> Bool {
return lhs.id == rhs.id
}
}请注意,尽管hash函数仅使用id属性,但也有必要重写==,否则在尝试重新加载行时会出现带有无效标识符的崩溃。
每秒钟随机选择的行被重新加载。当您运行代码时,您会看到这些随机选择的行更新了时间。
这是使用reloadItems的代码
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
guard let datasource = self.tableview.dataSource as? UITableViewDiffableDataSource<Section,RowData> else {
return
}
var snapshot = datasource.snapshot()
var rowIdentifers = Set<RowData>()
for _ in 0...Int.random(in: 1...self.arrItems.count) {
let randomIndex = Int.random(in: 0...self.arrItems.count-1)
self.arrItems[randomIndex].timeStamp = Date()
rowIdentifers.insert(self.arrItems[randomIndex])
}
snapshot.reloadItems(Array(rowIdentifers))
datasource.apply(snapshot)
}发布于 2020-11-12 11:53:47
我发布了同一个问题,但没有意识到。我首先通过将我的模型转换为类来完成这项工作。然后在调用'reloadItems‘之后调用'applySnapshot’。
func toggleSelectedStateForItem(at indexPath: IndexPath, animate: Bool = true) {
let item = dataSource.itemIdentifier(for: indexPath)!
var snapshot = dataSource.snapshot()
item.isSelected = !item.isSelected
snapshot.reloadItems([item])
dataSource.apply(snapshot)
}https://stackoverflow.com/questions/64081701
复制相似问题