At WWDC2019, Apple introduced a new way to set up data sources for use in UITableView and UICollectionView: UITableViewDiffableDataSource and UICollectionViewDiffableDataSource, support iOS13 and above version is used.

Here’s how our 10-year-old dataSource is set up:

  • Set the number of sections
  • Set item number;
  • Set the corresponding cell.

Setting dataSource like this often results in crashes like the one shown below:

When you run into this problem, it’s usually caused by arrays out of bounds in the process of manipulating indexPath and data sources, and sometimes it’s not easy to debug. Often you’ll solve the problem by calling reloadData(), but reloadData() doesn’t drive the drawing, which degrades the user experience. And sometimes you don’t want to refresh the entire view.

Next look at the use of DiffableDataSource

Because DiffableDataSource is similar to CollectionView in TableView, we use TableView to illustrate it.

  • First of all to understand one of the key things: NSDiffableDataSourceSnapshot: We can understand that it is a snapshot, we need to set the corresponding section and item for the snapshot, and apply the snapshot to the dataSource. Then we can modify the snapshot contents and change the display result.

  • Take a look at how apple define UITableViewDiffableDataSource:

    You can see hereSectionIdentifierTypeandItemIdentifierTypeIt’s all about complianceHashableThe goal is to ensure uniqueness.

  • We define a Hashable SectionIdentifierType and ItemIdentifierType:

    Enum Section: CaseIterable {case main} struct MyModel: Hashable { var title: String init(title: String) { self.title = title self.identifier = UUID() } private let identifier: UUID func hash(into hasher: inout Hasher) { hasher.combine(self.identifier) } }Copy the code
  • Define dataSource using Section, MyModel:

    private var dataSource: UITableViewDiffableDataSource<Section, MyModel>! = nil
    Copy the code
  • Set tableView UI, dataSource, create snapShot and apply it to dataSource:

    Model private lazy var mainModels = [MyModel(title: "Item1"), MyModel(title: "Item2"), MyModel(title: "Item2")), MyModel(title: "Item2")) "Item3")] // tableView private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .insetGrouped) tableView.translatesAutoresizingMaskIntoConstraints = false tableView.register(UITableViewCell.self, forCellReuseIdentifier: "identifier") tableView.delegate = self return tableView }() override func viewDidLoad() { super.viewDidLoad() SetupDataSource () // Set tableView UI setupDataSource() // Set tableView dataSource applySnapshot() // Set snapshot for dataSource } func setupTableView() { view.addSubview(tableView) NSLayoutConstraint.activate([ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } func setupDataSource() { dataSource = UITableViewDiffableDataSource<Section, MyModel>.init(tableView: self.tableView, cellProvider: {(tableView, indexPath, model) -> UITableViewCell? in let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) var content = cell.defaultContentConfiguration() content.text = model.title cell.contentConfiguration = The content return cell}) dataSource. DefaultRowAnimation =. Fade} func applySnapshot () {/ / insert sections, and the items in the snapshot var  snapshot = NSDiffableDataSourceSnapshot<Section, MyModel>() snapshot.appendSections([.main]) snapshot.appendItems(mainModels, toSection: .main) dataSource.apply(snapshot, animatingDifferences: true) }Copy the code

    The running effect is as follows:

  • When we need to modify the data, for example, delete the row after clicking, we can do it in two ways. Personally, the second one is more convenient and does not need to manually operate the item in snapshot:

    • Method 1 (not recommended, code is error-prone) :
      • withsnapshot()Get the current snapshot;
      • Delete the corresponding item from snapshot.
      • Delete model from mainModels;
      • Apply the changed snapshot to dataSource.
    extension ViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: Var snapshot = datasource.snapshot () var snapshot = datasource.snapshot ( MainModels [indexpath.row] snapshot.deleteItems([item]) // Delete model mainItems. Remove (at: DataSource. Apply (snapshot, animatingDifferences: true)}}Copy the code
    • Method 2:
      • Delete model from mainModels;
      • Generate a new empty snapshot;
      • Add section, item and other data to snapshot;
      • Apply the new snapshot to dataSource.
    extension ViewController: UITableViewDelegate {
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            mainItems.remove(at: indexPath.row)
            applySnapshot()
        }
    }
    Copy the code

    Operation effect:

References:

  • WWDC2019 – Advances in UI Data Sources
  • Modern table views with diffable data sources
  • Apple documentation – UITableViewDiffableDataSource
  • Apple documentation – NSDiffableDataSourceSnapshot
  • UITableView and UICollectionView’s new rendering mode DiffableDataSource