首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSDiffableDataSourceSnapshot‘`reloadItems`’是干什么的?

NSDiffableDataSourceSnapshot‘`reloadItems`’是干什么的?
EN

Stack Overflow用户
提问于 2020-09-26 19:25:08
回答 4查看 10.9K关注 0票数 37

我很难找到NSDiffableDataSourceSnapshot reloadItems(_:)的用法

  • 如果我要求重新加载的项不能等同于数据源中已经存在的项,则我会崩溃: 由于未命名的异常“NSInternalInconsistencyException”终止应用程序,原因:“试图重新加载快照中不存在的项标识符: ProjectName.ClassName
  • 但是,如果该项等同于数据源中已经存在的项,那么“重新加载”它有什么意义?

您可能会认为第二点的答案是:好吧,项目标识符对象的其他方面可能不是其等式的一部分,而是反映到了单元格接口中。但是我发现这不是真的;在调用reloadItems之后,表视图没有反映更改。

因此,当我想要更改一个项目时,我对快照所做的最后是在要替换的项之后的insert,然后是原始项的delete。没有快照replace方法,这正是我所希望的reloadItems会变成的方法。

(我对这些术语做了一次堆栈溢出搜索,发现很少--主要是几个对reloadItems的特殊用法(如如何使用可扩展的UITableView更新表单元格 )感到困惑的问题。所以我要问的是,这个方法有什么实际用途?)

好吧,没有什么比有一个最小的可重复的例子来玩,所以这里有一个。

使用模板iOS创建一个普通的普通ViewController项目,并将此代码添加到ViewController中。

我一片一片地拿着。首先,我们有一个作为项目标识符的结构。UUID是唯一的部分,所以均匀度和可操纵性仅取决于它:

代码语言:javascript
复制
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
    }
}

接下来,(假)表视图和不同的数据源:

代码语言:javascript
复制
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,它的booltrue。因此,现在设置一个按钮来调用此操作方法,该方法试图使用bool切换reloadItems值。

代码语言:javascript
复制
@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。我可以通过调用insertremove来解决这个问题,而这正是我所做的工作。

但我的问题是:如果不是为了这件事,那么reloadItems是为了什么呢?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2020-10-01 23:41:46

(我对问题中所展示的行为提出了错误意见,因为我不认为这是好行为。但是,就目前的情况而言,我想我可以猜测一下这个想法的意图。)

当您将快照告知reload某一项时,它不会读取您提供的项目的数据!它只是简单地查看项,作为识别数据源中已经存在的项的一种方式,您正在请求重新加载。

(因此,如果您提供的项与数据源中已经提供的项相等但不完全相同,则您所提供的项与数据源中已经存在的项之间的“差异”将与无关;数据源将永远不会被告知任何不同之处。)

然后,当您将快照apply到数据源时,数据源将通知表视图重新加载相应的单元格。这将导致再次调用数据源的单元格提供程序函数。

好的,因此调用数据源的单元提供程序函数,通常有三个参数:表视图、索引路径和数据源中的数据。但是我们刚刚说过,来自数据源的数据并没有改变。那么,重新装载有什么意义呢?

显然,答案是,预计单元提供程序函数将在其他地方查找,以获得(至少部分)新数据,以显示在新退出队列的单元中。您需要有某种类型的“后备存储”,以供单元提供程序查看。例如,您可能正在维护一个字典,其中键是单元标识符类型,值是可能重新加载的额外信息。

这必须是合法的,因为根据定义,单元标识符类型是可选的,因此可以用作字典键,而且单元格标识符必须在数据中是唯一的,否则数据源将拒绝数据(通过崩溃)。查找将是即时的,因为这是一本字典。

下面是一个完整的工作示例,您只需将其复制并粘贴到项目中即可。该表描绘了三个名字,以及一个星星,用户可以点击使星星被填充或空,表示最喜欢或不喜欢。名称存储在不同的数据源中,但最喜欢的状态存储在外部备份存储中。

代码语言:javascript
复制
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)
    }
}
票数 11
EN

Stack Overflow用户

发布于 2020-09-27 21:22:16

基于您的新示例代码,我同意,它看起来像一个bug。将reloadItems添加到快照时,它将正确地触发数据源闭包以请求更新的单元格,但传递给闭包的IdentifierType项是原始的,而不是reloadItems调用提供的新值。

如果我将您的UniBool结构更改为类,使其成为引用而不是值类型,则一切按预期进行(因为现在只有一个UniBool实例,而不是一个具有相同标识符的新实例)。

目前似乎有几个可能的工作--围绕:

  1. IdentifierType使用引用而不是值类型
  2. 使用额外的后备存储(例如数组),并在数据源闭包中通过indexPath访问它。

我不认为这两种都是理想的。

有趣的是,在将UniBool更改为类之后,我尝试创建一个与现有实例具有相同uuid的新UniBool实例,并重新加载该实例;代码崩溃时会出现一个异常,说明指定用于重新加载的无效项标识符;这听起来不正确;只有hashValue应该重要,而不是实际的对象引用。原始对象和新对象都具有相同的hashValue==返回true

原始答案

reloadItems可以工作,但是有两个要点:

  1. 您必须从数据源的当前snapshot开始,并对此调用reloadItems。不能创建新快照。
  2. 除了item之外,您不能依赖传递给CellProvider闭包的CellProvider--它并不代表支持模型(数组)中的最新数据。

第2点意味着您需要使用提供的indexPathitem.id从模型中获取更新的对象。

我创建了一个简单的示例,它在表行中显示当前时间;这是数据源结构:

代码语言:javascript
复制
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的代码

代码语言:javascript
复制
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)
}
票数 7
EN

Stack Overflow用户

发布于 2020-11-12 11:53:47

我发布了同一个问题,但没有意识到。我首先通过将我的模型转换为类来完成这项工作。然后在调用'reloadItems‘之后调用'applySnapshot’。

代码语言:javascript
复制
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)
}
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64081701

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档