Primeiros passos com UITableViewDiffableDataSource — Parte 2
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! :)
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 snapshot
irá adicionar um elemento novo, ao invés de alterar a nota existente.
Mas então, o que fazer?
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 oid
e não oindexPath
para a construção das células e atualizações datableView
.
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!
Pronto! Esse é o final do nosso estudo sobre UITableViewDiffableDataSource
.
O código final você encontra na branch study01-final
do 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! :)