First steps with UITableViewDiffableDataSource — Part 2
In the first part of this study, I explained some problems that we face when we use UITableViewDataSource
, and I presented an alternative, introduced by Apple on WWDC 2019: UITableViewDiffableDataSource
.
In the case you didn't read this study, you can check it here, if you want. You can check here the base project for this study and for the next ones.
Let's continue!
Where were we?
We just initialized the UITableViewDiffableDataSource
and applied the first snapshot
with it's initial elements. The problem is that we can't edit, move or add new elements.
Let's fix that step by step.
The first thing to understand is that UITableViewDiffableDataSource
has the responsibility to tell if the cell is editable or movable. It was a role of the UITableViewDelegate
, but now it belongs to UITableViewDiffableDataSource
.
We can remove this piece of code from protocol implementation of UITableViewDelegate
.
extension DiaryViewController: UITableViewDelegate {
//Delete all the other methods. Don't delete only the one below.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
//Don't delete it's implementation!
Now time to make our UITableViewDiffableDataSource
able to move or edit notes. To do that, we need to create a new class, subclass of UITableViewDiffableDataSource
. This new class should override the following methods:
@objc open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool@objc open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool
We don't have any situation that any of these method should return false
. So, both should only return true
.
Before we continue with dataSource
implementation we should fix something important.
Our dataSource
is a type that keep notes. Even it's showing correctly, we will have some problems.
When we update an element and apply a snapshot
, the tableView
will be updated based on the changes between the old and the current snapshot
. In our case, the hash
value is based only by the id. So, there's no change between the elements, because we don't update the id
when we edit a note. We just update the title and the message.
If we try to force an update, combining all the elements to have a different hash
, something like…
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(title)
hasher.combine(message)
}static func == (lhs: DiaryNote, rhs: DiaryNote) -> Bool {
return lhs.id == rhs.id &&
lhs.title == rhs.title &&
lhs.message == rhs.id
}
… we will have another problem! :(
In this case, when we change the title or message, we have a new note different from the old one. The snapshot
will add a new element, instead of updating the existing note.
Well… What to do?
An interesting approach would be, instead of using notes as one of the dataSource
types, we could use it's id's
.
Personal note: The approach of keeping an array of id's, and have the responsibility of sync it with the
tableView
first looked to me, like a return to the past problems. Also, it can lead to crash the app if we don't sync it correctly, like before. But, despite of that, I think it's safer, because we use theid
instead ofindexPath
to build the cells and updates thetableView
.
After that, we need to update everything on our code to conform to the new change.
Take your time to take a look in the code below, before you continue reading. :)
As you probably noticed, in the saving function, we are adding and updating the notes array
, every time we add or update a note. Then we apply the snapshot
. We want to have everything in sync!
Maybe you're asking to yourself why DiaryNote
is still Hashable
. We're only using the id
now. Yeah, you're right! We don't need it as Hashable
anymore.
But, as we're using firstIndex(of:
on save
function, it needs to conform at least toEquatable
.
The DiaryNote
code could be like that:
struct DiaryNote: Equatable {
let id = UUID()
var title: String
var message: String static func == (lhs: DiaryNote, rhs: DiaryNote) -> Bool {
return lhs.id == rhs.id
}
}
The dataSource
changed as well. It's an EditEnabledDiffableDataSource
now. It already changed to use the UUID
and it has as a closure
for the callback after deleting an element.
This deleteClosure
is used to delete the element in the controller
, because we can't access the notes array
in the new dataSource
.
Cool, isn’t it?
Let's take a look at the code!
Now we can run the project!
Voilá! Everything is working like before!
Done! This is the end of our study about UITableViewDIffableDataSource
.
The final code you can find at the study01-final
branch in the same project. If you want, you can check it here.
For a deep understanding, I suggest reading Apple documentation and the WWDC 2019 video when they introduced these topics. You can check the links right below:
See you next time! :)