Comment créer des listes flexibles: vue d'ensemble dynamique de UICollectionView - IGListKit

Les collections se trouvent dans de nombreuses applications mobiles - par exemple, il peut s'agir de listes de publications sur les réseaux sociaux, de recettes, de formulaires de commentaires et bien plus encore. UICollectionView est souvent utilisé pour les créer. Pour former une liste flexible, vous devez synchroniser le modèle de données et la vue, mais divers échecs sont possibles.



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





All Articles