首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Swift DiffableDataSource使插入和删除代替重新加载

Swift DiffableDataSource使插入和删除代替重新加载
EN

Stack Overflow用户
提问于 2020-06-08 16:52:49
回答 3查看 1.4K关注 0票数 10

我很难理解DiffableDataSource是如何工作的。我有这样的ViewModel

代码语言:javascript
复制
struct ViewModel: Hashable {
  var id: Int
  var value: String

  func hash(into hasher: inout Hasher) {
     hasher.combine(id)
  }
}

像上面的tableView一样,cachedItems填充了ViewModele。当API响应到达时,我希望添加一个新的响应,删除缺少的响应,刷新viewModel.value中已经存在的项,并最终对其进行排序。一切正常,除了一件事-重新加载项目。

我对DiffableDataSource的理解是,它比较item.hash()以检测项目是否已经存在,如果已经存在,那么如果是cachedItem != apiItem,则应该重新加载。不幸的是,这是不起作用的,快照确实删除&插入而不是重新加载。

DiffableDataSource应该这么做吗?

当然,我有一个解决方案--为了使它正常工作,我需要遍历cachedItems,当新条目包含相同的id时,我更新cachedItem,然后在没有动画的情况下更新applySnapshot,然后我最终可以使用动画applySnapshot删除/插入/排序动画。

但是这个解决方案似乎更像是一个黑客而不是一个有效的代码。有没有一种更清洁的方法来实现这一点?

更新:

有显示问题的代码。它应该在操场上工作。例如。items和newItems包含id == 0的viewModel。散列是相同的,所以diffableDataSource应该重新加载,因为字幕是不同的。但是有可见的删除/插入而不是重新加载。

代码语言:javascript
复制
import UIKit
import PlaygroundSupport

class MyViewController : UIViewController {
    let tableView = UITableView()

    var  diffableDataSource: UITableViewDiffableDataSource<Section, ViewModel>?

    enum SelectesItems {
        case items
        case newItems
    }

    var selectedItems: SelectesItems = .items

    let items: [ViewModel] = [ViewModel(id: 0, title: "Title1", subtitle: "Subtitle2"),
    ViewModel(id: 1, title: "Title2", subtitle: "Subtitle2"),
    ViewModel(id: 2, title: "Title3", subtitle: "Subtitle3"),
    ViewModel(id: 3, title: "Title4", subtitle: "Subtitle4"),
    ViewModel(id: 4, title: "Title5", subtitle: "Subtitle5")]

    let newItems: [ViewModel] = [ViewModel(id: 0, title: "Title1", subtitle: "New Subtitle2"),
    ViewModel(id: 2, title: "New Title 2", subtitle: "Subtitle3"),
    ViewModel(id: 3, title: "Title4", subtitle: "Subtitle4"),
    ViewModel(id: 4, title: "Title5", subtitle: "Subtitle5"),
    ViewModel(id: 5, title: "Title6", subtitle: "Subtitle6")]

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        self.view = view

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CellID")

        diffableDataSource = UITableViewDiffableDataSource<Section, ViewModel>(tableView: tableView, cellProvider: { (tableView, indexPath, viewModel) -> UITableViewCell? in
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "CellID")
            cell.textLabel?.text = viewModel.title
            cell.detailTextLabel?.text = viewModel.subtitle
            return cell
        })
        applySnapshot(models: items)

        let tgr = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        view.addGestureRecognizer(tgr)
    }

    @objc func handleTap() {
        switch selectedItems {
        case .items:
            applySnapshot(models: items)
            selectedItems = .newItems
        case .newItems:
           applySnapshot(models: newItems)
           selectedItems = .items
        }
    }

    func applySnapshot(models: [ViewModel]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, ViewModel>()
        snapshot.appendSections([.main])
        snapshot.appendItems(models, toSection: .main)
        diffableDataSource?.apply(snapshot, animatingDifferences: true)
    }
}

enum Section {
    case main
}

struct ViewModel: Hashable {
    let id: Int
    let title: String
    let subtitle: String

    func hash(into hasher: inout Hasher) {
       hasher.combine(id)
    }
}


// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-06-08 18:17:59

这是因为你实现得不对。

记住,哈斯可也意味着平等--两者之间存在着不可侵犯的关系。规则是两个相同的对象必须具有相同的哈希值。但是在您的ViewModel中,"equal“涉及比较所有三个属性,idtitlesubtitle --尽管hashValue没有,因为您实现了hash

换句话说,如果您实现了hash,那么您必须实现==才能完全匹配它:

代码语言:javascript
复制
struct ViewModel: Hashable {
    let id: Int
    let title: String
    let subtitle: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    static func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
        return lhs.id == rhs.id
    }
}

如果您进行了更改,您将发现表视图动画的行为与您预期的一样。

如果还希望表视图获取基础数据实际上已更改的事实,则还必须调用reloadData

代码语言:javascript
复制
    diffableDataSource?.apply(snapshot, animatingDifferences: true) {
        self.tableView.reloadData()
    }

(如果您有其他理由希望ViewModel的等价物能够继续包含所有三个属性,那么您需要两种类型,一种用于执行相等比较时使用-普通和简单,另一种用于涉及Hashable的上下文,例如不同的数据源、集合和字典键。)

票数 7
EN

Stack Overflow用户

发布于 2022-01-24 12:30:21

我建议阅读苹果的文章,完美地解释你面临的所有问题。简而言之,如果您想实现完美的更新,您需要:

  • 确保对UITableViewDiffableDataSource.ItemIdentifierType使用项标识符,而不是使用项。
  • 手动跟踪更新并将其添加到快照更改中:例如:snapshot.reloadItems(更新) //更新是一个已更新的项标识符数组 对于我的项目,我只是比较所有的新项目和以前的项目之前,它们改变了平等。
票数 0
EN

Stack Overflow用户

发布于 2022-06-14 11:51:59

我使用的可扩散数据源,组成布局与一个估计的细胞高度。

如果使用.reloadData(),将导致单元格跳转,因为.estimated(值)。

我将其修正如下:

代码语言:javascript
复制
diffableDataSource?.apply(snapshot, animatingDifferences: true) {
    self.collectionView.reloadData()
    // add this line here
    self.collectionView.collectionViewLayout.invalidateLayout()
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62267256

复制
相关文章

相似问题

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