Swift DiffableDataSource进行插入和删除操作,而不是重新加载

时间:2020-06-08 16:52:49

标签: swift diffabledatasource

我很难理解DiffableDataSource的工作原理。 我有这样的ViewModel

struct ViewModel: Hashable {
  var id: Int
  var value: String

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

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

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

DiffableDataSource应该这样做吗?

当然,我有一个解决方案-要使其正常工作,我需要遍历cachedItems,当新项目包含相同的ID时,我更新cachedItem,然后我套用不带动画的Snapshot,然后我终于可以将带动画的Snapshot删除/插入/排序动画。

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

更新:

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


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()

1 个答案:

答案 0 :(得分:3)

这是因为您错误地实现了Hashable。

请记住,Hashable的含义还等于Equatable-两者之间存在不可侵犯的关系。规则是两个相等的对象必须具有相等的哈希值。但是在您的ViewModel中,“等于”涉及比较所有三个属性idtitlesubtitle -尽管hashValue并非如此,因为您实现了{{1} }。

换句话说,如果实现hash,则必须实现hash使其完全匹配:

==

如果进行了更改,您会发现表视图动画的行为符合预期。

如果您还希望表视图显示基础数据实际上已经更改的事实,那么您还必须调用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

((如果您出于某些 other 原因希望ViewModel的Equatable继续包含所有三个属性,则您需要两种类型,一种用于在执行普通比较和普通比较时使用)简单,而另一个涉及涉及Hashable的上下文,例如可扩散的数据源,集合和字典键。)