在WWDC2019,苹果推出了一种在UITableView和UICollectionView使用的全新数据源的设置方式:UITableViewDiffableDataSourceUICol数组的定义lectionViewDiffableDataSource,支持iOS13及以上版本使用。

先来看看我们用了10年的dataSource是如何设置的:

  • 设置section数量;
  • 设置iios下载tapp小胖子em数量;
  • 设置相应的cell;

iOS DiffableDataSource的使用

这样设置dat数组和链表的区别aSource经常会出现像下面所示的crash:

iOS DiffableDataSource的使用

遇到这种问题的时候appreciate,通常是因为在操作indexPath和数据源的过程中导致的数组苹果官网界,有时候debug起来并不简单。经常你会通过调数组词多音字组词reloadData()解决问题,但是apple苹果官网reloadData()不带动画,这就降低了用户体验。而且有时候你并不希望刷新整个视图。

接下来看看DiffableDataSource的用法

因为DiffableDataSource在TableView和CollectionView的用法类似,所以下面用TableView来演示数组

  • 首先先了appstore解一个关键的东西:NSDiffableDataSourceSnapshot:我们可以理解它是一个快照,我们需要给这个快照设置相应的section和item,并将此快照apple苹果官网apply到dataSource上,后续可以修改快照的内容,改变显示的结果。

    iOS DiffableDataSource的使用

    iOS DiffableDataSource的使用

  • 再来看看苹果怎么定义UITableViewDiffableDataSource的:

    iOS DiffableDataSource的使用
    这里苹果手机看得出SectionIdentifierTypeItemIdentifierType都是需要遵从Hashable的,目的是确保唯一性。

  • 我们先定义一个遵从HashableSecappletionIdentifierTypeItemIdentifierType

    // 枚举默认就是Hashable类型
    enum Section: CaseIterable {
        case main
    }
    // 自定义类型需要遵从Hashable协议
    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)
        }
    }
    
  • 定义使用SectionMyMapple storeodelappearancedataSource:

    private var dataSource: UITableViewDiffableDataSource<Section, MyModel>! = nil
    
  • 设置tableView的UI、dataSource,生成snapShot并将它用到dataSource上:

    // 这是即将用来展示的models
    private lazy var mainModels = [MyModel(title: "Item1"),
                                   MyModel(title: "Item2"),
                                   MyModel(title: "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()
        setupTableView() // 设置tableView UI
        setupDataSource() // 设置tableView的dataSource
        applySnapshot() // 给dataSource设置snapshot
    }
    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 = content
            return cell
        })
        dataSource.defaultRowAnimation = .fade
    }
    func applySnapshot() {
        // 往snapshot里插入sections和items
        var snapshot = NSDiffableDataSourceSnapshot<Section, MyModel>()
        snapshot.appendSections([.main])
        snapshot.appendItems(mainModels, toSection: .main)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
    

    运行效果如下:

    iOS DiffableDataSource的使用

  • 当我们需要修改数据,比如点击了就删掉该行,可以用2种方式实现,个人觉得第2种比较方便一点,不用手动操作snapshot中的item:

    • 方法1(不推荐,代码容易出错):
      • snapshot()取到当ios是什么意思前的snapshot;
      • 删掉snapshot中对应的item;
      • 删掉mainModels中的mo数组去重del;
      • 将改变后的snapshot运用到dataSource上;苹果12
    extension ViewController: UITableViewDelegate {
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            // 取到当前的snapshot
            var snapshot = dataSource.snapshot()
            // 删掉snapshot中对应的item
            let item = mainModels[indexPath.row]
            snapshot.deleteItems([item])
            // 删掉mainModels中的model
            mainItems.remove(at: indexPath.row)
            // 将改变后的snapshot运用到dataSource上
            dataSource.apply(snapshot, animatingDifferences: true)
        }
    }
    
    • 方法2:
      • 删掉mainModels中的model;
      • 生成一个新的空snapshot;
      • 重新将section、item等数据加到snapshot中;
      • 将新的snapshot运用到dataSource上去;
    extension ViewController: UITableViewDelegate {
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            mainItems.remove(at: indexPath.row)
            applySnapshot()
        }
    }
    

    运行效果:

    iOS DiffableDataSource的使用

参考资料:

  • WWDC2019 – Advances in UI Data Sources
  • Mode苹果12rn table views with diffablApplee data sources
  • Apple documentation – UITableViewDiffableDataSource
  • Apple documentation – NSDiffableDataSourceSnapsho数组公式t
  • UITableView和UICollectionView的新渲染方式DiffableDataSource