39.%d\n Xcode — UITableViewDiffableDataSource<Section, Item>(二)

春麗 S.T.E.M.
8 min readDec 28, 2021

--

comparison of two canMoveRowAt methods

自從改用UITableViewDiffableDataSource後,似乎多種的tableViewCell變得容易處理了,因為本來塞入dataSource在我們試著歸類時,大致上需要兩種訊息,一個是這個cell在哪個section,一個是這個cell要放入什麼item,而diffableDataSource就是在幫我們做這種事。

本來的做法是

extension CodeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}}

第一個方法裡要switch section,分別比對第幾個section塞入的資料數量,讓tableView知道這個section有多少row;第二個方法裡要switch indexPath.section,分別塞入不同種的cell,而cell的資料以indexPath.row來表示不同列的資料項。如果沒有那麼多種cell,那就不用比對section了。

而在UITableViewDiffableDataSource的做法是

dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in    return cell
})

在宣告dataSource時,藉由tableView與cellProvider,就能做到上面兩個方法能做到的事,主要因為有indexPath與itemIdentifier,後者是itemIdentifierType型別,不過在這裡若只有一個section,塞進去的可能就只是item了,那從itemIdentifier再讀到細項去設定你的cell。

雖然看起來diffableDataSource簡化遵循dataSource的兩個方法,但仍有麻煩之處,因為上述只做了一半,還需要這樣做才能真正更新tableView的data

        var snapShot = dataSource.snapshot()
snapShot.appendSections([0])
snapShot.appendItems(videoGames, toSection: 0)
dataSource.applySnapshotUsingReloadData(snapShot, completion: nil)

snapShot要加入sections,還要在指定section加入items,最後還要更新data才真正完成,當然,這個section是定為Int,所以就以數字表示。如果要一個個加入資料,可能會有這樣的reloadData方法,如下:

private func updateDataSource() {
var snapShot = NSDiffableDataSourceSnapshot<Section, Trip>()
snapShot.appendSections([.phase])
snapShot.appendItems(tripSet, toSection: .phase)
dataSource.apply(snapShot, animatingDifferences: true, completion: nil)
}

snapShot做同樣的操作,而下方兩種apply是差不多的東西。

表格移動

雖然diffableDataSource很有潛力,但在使用過去常用的方法時卻出了問題,首先,我想要使用左滑刪除,費了一番工夫,這不打緊,因為解決了,接著,我又想要移動列位置,卡住了。

過去移動位置,在canMoveRowAt返回true,在moveRowAt裡,我們會這麼做

        let moveLine = storyStates[sourceIndexPath.row]
storyStates.remove(at: sourceIndexPath.row)
storyStates.insert(moveLine, at: destinationIndexPath.row)

將資料陣列刪除sourceRow,接著insert這個刪除的資料到destinationRow。那在diffableDataSource呢?如果這樣用會報錯……

主要還是因為它必須要apply snapshot,但即便我update了,卻仍然報錯,後來是用下面這個方法

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {        guard let fromGame = itemIdentifier(for: sourceIndexPath), sourceIndexPath != destinationIndexPath else {
return
}

var snap = snapshot()
snap.deleteItems([fromGame])

if let toGame = itemIdentifier(for: destinationIndexPath) {
let isAfter = destinationIndexPath.row > sourceIndexPath.row

if isAfter {
snap.insertItems([fromGame], afterItem: toGame)
} else {
snap.insertItems([fromGame], beforeItem: toGame)
}
} else {
snap.appendItems([fromGame], toSection: sourceIndexPath.section)
}

apply(snap, animatingDifferences: false, completion: nil)
}

先有起始點的資料,並且保證它不等同於終點的資料,才會滿足下方,因為資料看起來其實是要從snapshot裡面去處理,而不是從資料來源處理再重新塞入,這時snap就可以deleteItem了,不過這只完成上半部。

後有終點的資料,如果終點位置大於起點位置,snap就把終點資料放到起點資料後,反之放在前面。如果只有一個section,後面的else就不需要了,最後再apply,實在是麻煩多了,這下也許以diffableDataSource塞入tableView資料需要考慮一下了。

以下reference

最後附上

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet