Primeiros passos com UITableViewDiffableDataSource — Parte 2

Roberto Sampaio
My iOS Studies [pt-br]
4 min readApr 11, 2021

Na parte 1 desse estudo, eu expliquei alguns problemas que enfrentávamos ao utilizarmos UITableViewDataSource, e apresentei a alternativa que a Apple introduziu na WWDC 2019: UITableViewDiffableDataSource.

Caso não tenha visto esse estudo você pode conferir ele aqui. Você pode também conferir aqui o projeto base para os próximos estudos que estarei escrevendo. Como esse é o primeiro estudo, ele é a base desse projeto também!

Dadas essas informações, podemos continuar agora! :)

Photo by Jessie Kenaston on Unsplash

Onde paramos?

Acabamos de inicializar o nosso UITableViewDiffableDataSource, e aplicamos o primeiro snapshot com os elementos iniciais nele. O problema é que não conseguimos mais editar, mover e adicionar novos elementos.

Vamos corrigir isso aos poucos!

A primeira coisa que precisamos entender é que, agora que estamos utilizando o UITableViewDiffableDataSource, ele também será responsável por dizer se a célula é ou não editável ou pode ou não ser movida. O papel que antes pertencia ao UITableViewDelegate agora pertence a ele.

Sendo assim, podemos remover esse código da implementação do protocolo UITableViewDelegate.

extension DiaryViewController: UITableViewDelegate {
//Apague todos os outros métodos. Deixe apenas a implementação do método de seleção das linhas.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
//Não apague essa implementação!

Agora precisamos fazer com que o nosso UITableViewDiffableDataSource nos permita mover e editar notas. Para fazer isso, precisamos criar uma classe nova, subclasse deUITableViewDiffableDataSource e que sobrescreva os métodos abaixo:

@objc open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool@objc open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool

Como não temos nenhum critério para editar ou mover células, para ambos podemos apenas retornar true.

Mas, vamos voltar para a criação desse arquivo em breve. Existe algo bem importante que precisa ser resolvido antes de continuarmos.

Em nosso código de agora, o nosso dataSource contém um conjunto de notas. Isso vai gerar um certo problema.

Quando alteramos um elemento e pedimos para o snapshot atualizar para nós, o snapshot vai atualizar de acordo com as mudanças que existiram. No caso, como nosso valor de hash se baseia unicamente pelo id, não haverá mudança, pois o id de uma nota não vai ser alterado na atualização da mesma.

E, caso tentemos combinar todos os elementos para montar o hash, com o intuito de forçar uma atualização, algo como…

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
}

… teríamos outro problema! :(

Nesse caso, ao mudarmos o título ou a mensagem, teremos uma nota diferente da anterior. Então o snapshotirá adicionar um elemento novo, ao invés de alterar a nota existente.

Mas então, o que fazer?

Photo by Jonathan Cosens Photography on Unsplash

Uma abordagem interessante seria, ao invés de utilizar os elementos como um dos tipos para o dataSource, utilizar os seus id's.

Nota pessoal: A abordagem de guardar um array de id's, e ter de manter ele sincronizado com a tableView, a princípio me soou como uma volta aos problemas antigos. Inclusive isso pode gerar um crash no aplicativo se feito erroneamente. Mas, apesar disso, achei bem mais simples e seguro, porque utilizamos o id e não o indexPath para a construção das células e atualizações da tableView.

Após essa alteração, precisamos atualizar as funções do nosso código para conformar a essa mudança.

Analise um pouco o código abaixo antes de continuar a leitura! :)

Como você pode notar, na função de salvar, estamos fazendo as etapas de adicionar ou atualizar as notas no array de notas que temos, e então aplicamos o snapshot. Queremos manter tudo sincronizado!

Talvez você esteja se perguntando porque mantivemos o DiaryNote ainda está implementado como Hashable, já que estamos utilizando apenas o id. Tem razão! Não precisamos mais dele como Hashable.

Mas, como estamos utilizando o firstIndex(of: na função de save, ele precisa conformar pelo menos com o Equatable.

O código final da DiaryNote poderia ficar assim:

struct DiaryNote: Equatable {
let id = UUID()
var title: String
var message: String
static func == (lhs: DiaryNote, rhs: DiaryNote) -> Bool {
return lhs.id == rhs.id
}
}

Nosso dataSource também mudou. Ele é um EditEnabledDiffableDataSource agora. Ele possui uma closure para o caso de deleção de um elemento, e já está refletindo a alteração para o UUID.

Essa deleteClosure serve justamente para esse propósito, pois não temos acesso às notas na classe do novo dataSource.

Elegante, não? :D

Agora vamos ao código!

Esse é todo o código do nosso novo dataSource, e agora podemos executar o projeto!

Voilá! Tudo funcionando como antes!

Photo by Joe Caione on Unsplash

Pronto! Esse é o final do nosso estudo sobre UITableViewDiffableDataSource.

O código final você encontra na branch study01-finaldo mesmo projeto. Se quiser, pode conferir direto aqui.

Para um estudo mais profundo, sugiro a leitura da documentação da Apple e o vídeo da WWDC 2019 que falam sobre esse respectivo assunto. Deixarei os links logo abaixo.

Até a próxima! :)

Link do projeto inicial.

Link do projeto final.

--

--

Roberto Sampaio
My iOS Studies [pt-br]

Servo de Jesus, aprendendo a escrever, desenvolver software, lutar e tocar guitarra. Aprendendo, sempre.