Dans cet article, nous examinerons le framework IGListKitcréé par l'équipe de développement d'Instagram pour résoudre le problème ci-dessus. Il vous permet de configurer une collection avec plusieurs types de cellules et de les réutiliser en quelques lignes. Dans le même temps, le développeur a la possibilité d'encapsuler la logique du framework à partir du ViewController principal. Ensuite, nous parlerons des fonctionnalités de création d'une collection dynamique et de gestion des événements. La revue peut être utile pour les développeurs débutants et expérimentés qui souhaitent maîtriser un nouvel outil.
Comment travailler avec IGListKit
L'utilisation du framework IGListKit est globalement similaire à l'implémentation standard d'UICollectionView. De plus, nous avons:
- modèle de données;
- ViewController;
- cellules de la collection UICollectionViewCell.
De plus, il existe des classes d'assistance:
- SectionController - responsable de la configuration des cellules dans la section courante;
- SectionControllerModel - chaque section a son propre modèle de données;
- UICollectionViewCellModel - pour chaque cellule, également son propre modèle de données.
Considérons leur utilisation plus en détail.
Créer un modèle de données
Tout d'abord, nous devons créer un modèle, qui est une classe, pas une structure. Cette fonctionnalité est due au fait que IGListKit est écrit 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
}
}
Étendons maintenant le modèle avec le protocole 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 vous permet d'identifier et de comparer de manière unique des objets afin de mettre à jour automatiquement les données dans UICollectionView sans erreur.
Le protocole nécessite la mise en œuvre de deux méthodes:
func diffIdentifier() -> NSObjectProtocol
Cette méthode renvoie un identifiant unique pour le modèle utilisé pour la comparaison.
func isEqual(toDiffableObject object: ListDiffable?) -> Bool
Cette méthode est utilisée pour comparer deux modèles entre eux.
Lorsque vous travaillez avec IGListKit, il est courant d'utiliser des modèles pour créer et faire fonctionner chacune des cellules et SectionController. Ces modèles sont créés selon les règles décrites ci-dessus. Un exemple peut être trouvé dans le référentiel .
Synchroniser une cellule avec un modèle de données
Après avoir créé le modèle de cellule, vous devez synchroniser les données avec le remplissage de la cellule elle-même. Disons que nous avons déjà une ExpandingCell . Ajoutons-y la possibilité de travailler avec IGListKit et l'étendons pour fonctionner avec le protocole 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")
}
}
Ce protocole nécessite l'implémentation de la méthode func bindViewModel (_ viewModel: Any) . Cette méthode met à jour les données de la cellule.
Former une liste de cellules - SectionController
Une fois les modèles de données et les cellules prêts, nous pouvons commencer à les utiliser et à former une liste. Créons une classe SectionController.
final class InfoSectionController: ListBindingSectionController<ListDiffable> {
weak var delegate: InfoSectionControllerDelegate?
override init() {
super.init()
dataSource = self
}
}
Notre classe hérite de
ListBindingSectionController<ListDiffable>
Cela signifie que tout modèle conforme à ListDiffable fonctionnera avec SectionController.
Nous devons également développer le protocole 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)
}
}
Pour respecter le protocole, nous mettons en œuvre 3 méthodes:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable]
Cette méthode crée un tableau de modèles dans l'ordre dans lequel ils sont affichés dans UICollectionView.
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable
La méthode renvoie la cellule souhaitée en fonction du modèle de données. Dans cet exemple, le code de connexion de la cellule est extrait séparément, pour plus de détails, voir le référentiel .
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize
La méthode renvoie la taille de chaque cellule.
Configuration du ViewController
Connectons le ListAdapter et le modèle de données au ViewController existant, et remplissons-le également. ListAdapter vous permet de créer et de mettre à jour UICollectionView avec des cellules.
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
}
}
Pour fonctionner correctement, l'adaptateur est nécessaire pour développer le protocole 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
}
}
Le protocole implémente 3 méthodes:
func objects(for listAdapter: ListAdapter) -> [ListDiffable]
La méthode nécessite de renvoyer un tableau du modèle rempli pour le SectionController.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
Cette méthode initialise le SectionController dont nous avons besoin.
func emptyView(for listAdapter: ListAdapter) -> UIView?
Renvoie la vue qui s'affiche lorsque des cellules sont manquantes.
Sur ce, vous pouvez démarrer le projet et vérifier le travail - l'UICollectionView doit être généré. De plus, puisque nous avons abordé les listes dynamiques dans notre article, nous ajouterons la gestion des clics de cellule et afficher une cellule imbriquée.
Gestion des événements de clic
Nous devons étendre le SectionController avec le protocole ListBindingSectionControllerSelectionDelegate et ajouter la conformité du protocole dans l'initialiseur.
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)
}
}
La méthode suivante est appelée lorsqu'un clic est effectué sur une cellule:
func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any)
Utilisons un délégué pour mettre à jour le modèle de données.
protocol InfoSectionControllerDelegate: class {
func sectionControllerDidTapField(_ field: ExpandingCellModel)
}
Nous allons étendre le ViewController et maintenant, en cliquant sur la cellule ExpandingCellModel dans le modèle de données de l' entreprise , nous allons changer la propriété isOpened . L'adaptateur mettra ensuite à jour l'état de UICollectionView, et la méthode suivante de SectionController dessinera la nouvelle cellule ouverte:
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)
}
}
Résumer
Dans cet article, nous avons examiné les fonctionnalités de création d'une collection dynamique à l'aide d'IGListKit et de la gestion des événements. Bien que nous n'ayons abordé qu'une partie des fonctions possibles du framework, même cette partie peut être utile au développeur dans les situations suivantes:
- pour créer rapidement des listes flexibles;
- pour encapsuler la logique de collecte du ViewController principal, le chargeant ainsi;
- pour configurer une collection avec plusieurs types de cellules et les réutiliser.
! .
gif