Neste artigo, consideraremos a estrutura IGListKitcriado pela equipe de desenvolvimento do Instagram para resolver o problema acima. Ele permite que você configure uma coleção com vários tipos de células e as reutilize em apenas algumas linhas. Ao mesmo tempo, o desenvolvedor tem a capacidade de encapsular a lógica do framework do ViewController principal. A seguir, falaremos sobre os detalhes da criação de uma coleção dinâmica e do tratamento de eventos. A revisão pode ser útil para iniciantes e desenvolvedores experientes que desejam dominar uma nova ferramenta.

Como trabalhar com IGListKit
O uso da estrutura IGListKit é amplamente semelhante à implementação UICollectionView padrão. Além disso, temos:
- modelo de dados;
- ViewController;
- células da coleção UICollectionViewCell.
Além disso, existem classes auxiliares:
- SectionController - responsável pela configuração das células na seção atual;
- SectionControllerModel - cada seção tem seu próprio modelo de dados;
- UICollectionViewCellModel - para cada célula, também seu próprio modelo de dados.
Vamos considerar seu uso com mais detalhes.
Criação de um modelo de dados
Primeiro, precisamos criar um modelo, que é uma classe, não uma estrutura. Este recurso é devido ao fato de que IGListKit é escrito em Objective-C.
final class Company {
let id: String
let title: String
let logo: UIImage
let logoSymbol: UIImage
var isExpanded: Bool = false
init(id: String, title: String, logo: UIImage, logoSymbol: UIImage) {
self.id = id
self.title = title
self.logo = logo
self.logoSymbol = logoSymbol
}
}
Agora vamos estender o modelo com o protocolo ListDiffable .
extension Company: ListDiffable {
func diffIdentifier() -> NSObjectProtocol {
return id as NSObjectProtocol
}
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
guard let object = object as? Company else { return false }
return id == object.id
}
}
ListDiffable permite que você identifique e compare objetos de maneira única para atualizar automaticamente os dados dentro do UICollectionView sem erros.
O protocolo requer a implementação de dois métodos:
func diffIdentifier() -> NSObjectProtocol
Este método retorna um identificador exclusivo para o modelo usado para comparação.
func isEqual(toDiffableObject object: ListDiffable?) -> Bool
Este método é usado para comparar dois modelos entre si.
Ao trabalhar com IGListKit, é comum usar modelos para criar e operar cada uma das células e SectionController. Esses modelos são criados de acordo com as regras descritas acima. Um exemplo pode ser encontrado no repositório .
Sincronizando uma célula com um modelo de dados
Depois de criar o modelo de célula, você precisa sincronizar os dados com o preenchimento da própria célula. Digamos que já tenhamos uma ExpandingCell definida . Vamos adicionar a capacidade de trabalhar com IGListKit e estendê-la para trabalhar com o protocolo ListBindable .
extension ExpandingCell: ListBindable {
func bindViewModel(_ viewModel: Any) {
guard let model = viewModel as? ExpandingCellModel else { return }
logoImageView.image = model.logo
titleLable.text = model.title
upDownImageView.image = model.isExpanded
? UIImage(named: "up")
: UIImage(named: "down")
}
}
Este protocolo requer a implementação do método func bindViewModel (_ viewModel: Any) . Este método atualiza os dados na célula.
Formando uma lista de células - SectionController
Depois de preparar os modelos de dados e células, podemos começar a usá-los e formar uma lista. Vamos criar uma classe SectionController.
final class InfoSectionController: ListBindingSectionController<ListDiffable> {
weak var delegate: InfoSectionControllerDelegate?
override init() {
super.init()
dataSource = self
}
}
Nossa classe herda de
ListBindingSectionController<ListDiffable>
Isso significa que qualquer modelo que esteja em conformidade com ListDiffable funcionará com SectionController.
Também precisamos expandir o protocolo SectionController, ListBindingSectionControllerDataSource .
extension InfoSectionController: ListBindingSectionControllerDataSource {
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] {
guard let sectionModel = object as? InfoSectionModel else {
return []
}
var models = [ListDiffable]()
for item in sectionModel.companies {
models.append(
ExpandingCellModel(
identifier: item.id,
isExpanded: item.isExpanded,
title: item.title,
logo: item.logoSymbol
)
)
if item.isExpanded {
models.append(
ImageCellModel(logo: item.logo)
)
}
}
return models
}
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable {
let cell = self.cell(for: viewModel, at: index)
return cell
}
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize {
let width = collectionContext?.containerSize.width ?? 0
var height: CGFloat
switch viewModel {
case is ExpandingCellModel:
height = 60
case is ImageCellModel:
height = 70
default:
height = 0
}
return CGSize(width: width, height: height)
}
}
Para cumprir o protocolo, implementamos 3 métodos:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable]
Este método constrói uma matriz de modelos na ordem em que aparecem no UICollectionView.
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable
O método retorna a célula desejada de acordo com o modelo de dados. Neste exemplo, o código para conectar a célula é retirado separadamente, para mais detalhes, consulte o repositório .
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize
O método retorna o tamanho de cada célula.
Configurando o ViewController
Vamos conectar o ListAdapter e o modelo de dados ao ViewController existente e também preenchê-lo. ListAdapter permite criar e atualizar UICollectionView com células.
class ViewController: UIViewController {
var companies: [Company]
private lazy var adapter = {
ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
required init?(coder: NSCoder) {
self.companies = [
Company(
id: "ss",
title: "SimbirSoft",
logo: UIImage(named: "ss_text")!,
logoSymbol: UIImage(named: "ss_symbol")!
),
Company(
id: "mobile-ss",
title: "mobile SimbirSoft",
logo: UIImage(named: "mobile_text")!,
logoSymbol: UIImage(named: "mobile_symbol")!
)
]
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
}
private func configureCollectionView() {
adapter.collectionView = collectionView
adapter.dataSource = self
}
}
Para funcionar corretamente, o adaptador é necessário expandir o protocolo do ViewController ListAdapterDataSource .
extension ViewController: ListAdapterDataSource {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return [
InfoSectionModel(companies: companies)
]
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
let sectionController = InfoSectionController()
return sectionController
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
return nil
}
}
O protocolo implementa 3 métodos:
func objects(for listAdapter: ListAdapter) -> [ListDiffable]
O método requer o retorno de um array do modelo preenchido para o SectionController.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
Este método inicializa o SectionController de que precisamos.
func emptyView(for listAdapter: ListAdapter) -> UIView?
Retorna a visualização que é exibida quando células estão faltando.
Com isso você pode iniciar o projeto e verificar o trabalho - o UICollectionView deve ser formado. Além disso, como tocamos nas listas dinâmicas em nosso artigo, adicionaremos manipulação para cliques em células e exibir uma célula aninhada.
Tratamento de eventos de clique
Precisamos estender SectionController com o protocolo ListBindingSectionControllerSelectionDelegate e adicionar conformidade de protocolo no inicializador.
dataSource = self
extension InfoSectionController: ListBindingSectionControllerSelectionDelegate {
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any) {
guard let cellModel = viewModel as? ExpandingCellModel
else {
return
}
delegate?.sectionControllerDidTapField(cellModel)
}
}
O método a seguir é chamado quando uma célula é clicada:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any)
Vamos usar um delegado para atualizar o modelo de dados.
protocol InfoSectionControllerDelegate: class {
func sectionControllerDidTapField(_ field: ExpandingCellModel)
}
Vamos estender o ViewController e agora, ao clicar na célula ExpandingCellModel do modelo de dados da empresa , vamos alterar a propriedade isOpened . O adaptador irá então atualizar o estado de UICollectionView, e o seguinte método do SectionController desenhará a nova célula aberta:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable]
extension ViewController: InfoSectionControllerDelegate {
func sectionControllerDidTapField(_ field: ExpandingCellModel) {
guard let company = companies.first(where: { $0.id == field.identifier })
else { return }
company.isExpanded.toggle()
adapter.performUpdates(animated: true, completion: nil)
}
}
Resumindo
Neste artigo, examinamos os recursos de criação de uma coleção dinâmica usando IGListKit e manipulação de eventos. Embora tenhamos tocado apenas em uma parte das funções possíveis do framework, mesmo esta parte pode ser útil para o desenvolvedor nas seguintes situações:
- para criar listas flexíveis rapidamente;
- para encapsular a lógica da coleção do ViewController principal, carregando-a;
- para configurar uma coleção com vários tipos de células e reutilizá-los.
! .
gif
