En este artículo, consideraremos el marco IGListKitcreado por el equipo de desarrollo de Instagram para resolver el problema anterior. Le permite configurar una colección con varios tipos de celdas y reutilizarlas en unas pocas líneas. Al mismo tiempo, el desarrollador tiene la capacidad de encapsular la lógica del marco desde el ViewController principal. A continuación, hablemos de las características de crear una colección dinámica y manejar eventos. La revisión puede ser útil tanto para principiantes como para desarrolladores experimentados que quieran dominar una nueva herramienta.
Cómo trabajar con IGListKit
El marco IGListKit es muy similar a la implementación estándar de UICollectionView. Además, tenemos:
- modelo de datos;
- ViewController;
- celdas de la colección UICollectionViewCell.
Además, hay clases de ayuda:
- SectionController : responsable de configurar las celdas en la sección actual;
- SectionControllerModel : cada sección tiene su propio modelo de datos;
- UICollectionViewCellModel : para cada celda, también su propio modelo de datos.
Consideremos su uso con más detalle.
Creando un modelo de datos
Primero, necesitamos crear un modelo, que es una clase, no una estructura. Esta característica se debe al hecho de que IGListKit está escrito en 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
}
}
Ahora ampliemos el modelo con el 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 le permite identificar y comparar objetos de forma única para actualizar automáticamente los datos sin errores dentro de UICollectionView.
El protocolo requiere la implementación de dos métodos:
func diffIdentifier() -> NSObjectProtocol
Este método devuelve un identificador único para el modelo utilizado para la comparación.
func isEqual(toDiffableObject object: ListDiffable?) -> Bool
Este método se utiliza para comparar dos modelos entre sí.
Cuando se trabaja con IGListKit, es común usar modelos para crear y operar cada una de las celdas y SectionController. Estos modelos se crean de acuerdo con las reglas descritas anteriormente. Se puede ver un ejemplo en el repositorio .
Sincronizar una celda con un modelo de datos
Después de crear el modelo de celda, debe sincronizar los datos con el llenado de la celda. Digamos que ya tenemos una ExpandingCell diseñada . Agreguemos la capacidad de trabajar con IGListKit y extendamos para que funcione con el 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 requiere la implementación del método func bindViewModel (_ viewModel: Any) . Este método actualiza los datos de la celda.
Formar una lista de celdas - SectionController
Una vez que tengamos listos los modelos de datos y las celdas, podemos comenzar a usarlos y formar una lista. Creemos una clase SectionController.
final class InfoSectionController: ListBindingSectionController<ListDiffable> {
weak var delegate: InfoSectionControllerDelegate?
override init() {
super.init()
dataSource = self
}
}
Nuestra clase hereda de
ListBindingSectionController<ListDiffable>
Esto significa que cualquier modelo que se ajuste a ListDiffable funcionará con SectionController.
También necesitamos expandir el protocolo de 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 cumplir con el protocolo, implementamos 3 métodos:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable]
Este método forma una matriz de modelos en el orden en que se muestran en UICollectionView.
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable
El método devuelve la celda deseada según el modelo de datos. En este ejemplo, el código para conectar la celda se extrae por separado, para más detalles, consulte el repositorio .
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize
El método devuelve el tamaño de cada celda.
Configuración de ViewController
Conectemos el ListAdapter y el modelo de datos al ViewController existente, y también lo llenamos. ListAdapter le permite crear y actualizar UICollectionView con celdas.
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 que funcione correctamente, el adaptador es necesario para expandir el protocolo ListAdapterDataSource de ViewController .
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
}
}
El protocolo implementa 3 métodos:
func objects(for listAdapter: ListAdapter) -> [ListDiffable]
El método requiere devolver una matriz del modelo completo para SectionController.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
Este método inicializa el SectionController que necesitamos.
func emptyView(for listAdapter: ListAdapter) -> UIView?
Devuelve la vista que se muestra cuando faltan celdas.
En esto, puede iniciar el proyecto y verificar el trabajo; se debe generar UICollectionView. Además, dado que tocamos las listas dinámicas en nuestro artículo, agregaremos manejo para clics de celda y mostraremos una celda anidada.
Manejo de eventos de clic
Necesitamos extender SectionController con el protocolo ListBindingSectionControllerSelectionDelegate y agregar cumplimiento de protocolo en el 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)
}
}
Se llama al siguiente método cuando se hace clic en una celda:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any)
Usemos un delegado para actualizar el modelo de datos.
protocol InfoSectionControllerDelegate: class {
func sectionControllerDidTapField(_ field: ExpandingCellModel)
}
Extenderemos ViewController y ahora, al hacer clic en la celda ExpandingCellModel en el modelo de datos de la Compañía , cambiaremos la propiedad isOpened . El adaptador actualizará el estado de UICollectionView y el siguiente método de SectionController dibujará la nueva celda abierta:
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)
}
}
Resumiendo
En este artículo, examinamos las características de crear una colección dinámica usando IGListKit y manejo de eventos. Aunque solo hemos tocado una parte de las posibles funciones del framework, incluso esta parte puede ser útil para el desarrollador en las siguientes situaciones:
- para crear listas flexibles rápidamente;
- para encapsular la lógica de la colección desde el ViewController principal, cargándolo así;
- para configurar una colección con varios tipos de celdas y reutilizarlas.
! .
gif