So erstellen Sie flexible Listen: eine Übersicht über das dynamische UICollectionView - IGListKit

Sammlungen sind in vielen mobilen Anwendungen verfügbar. Sie können beispielsweise Listen von Veröffentlichungen in sozialen Netzwerken, Rezepte, Feedbackformulare und vieles mehr sein. UICollectionView wird häufig verwendet, um sie zu erstellen. Um eine flexible Liste zu erstellen, müssen Sie das Datenmodell und die Ansicht synchronisieren. Es sind jedoch verschiedene Fehler möglich.



In diesem Artikel werden wir das IGListKit- Framework betrachten Erstellt vom Instagram-Entwicklerteam, um das oben genannte Problem zu lösen. Sie können eine Sammlung mit mehreren Zelltypen einrichten und diese in nur wenigen Zeilen wiederverwenden. Gleichzeitig kann der Entwickler die Logik des Frameworks vom Haupt-ViewController aus kapseln. Als Nächstes werden die Besonderheiten beim Erstellen einer dynamischen Sammlung und beim Behandeln von Ereignissen erläutert. Die Überprüfung kann sowohl für Anfänger als auch für erfahrene Entwickler nützlich sein, die ein neues Tool beherrschen möchten.







So arbeiten Sie mit IGListKit



Die Verwendung des IGListKit-Frameworks ähnelt weitgehend der Standardimplementierung von UICollectionView. Darüber hinaus haben wir:



  • Datenmodell;
  • ViewController;
  • Zellen der UICollectionViewCell-Auflistung.


Darüber hinaus gibt es Hilfsklassen:



  • SectionController - verantwortlich für die Konfiguration der Zellen im aktuellen Abschnitt;
  • SectionControllerModel - Jeder Abschnitt hat sein eigenes Datenmodell.
  • UICollectionViewCellModel - für jede Zelle auch ein eigenes Datenmodell.


Lassen Sie uns ihre Verwendung genauer betrachten.



Erstellen eines Datenmodells



Zuerst müssen wir ein Modell erstellen, das eine Klasse und keine Struktur ist. Diese Funktion ist darauf zurückzuführen, dass IGListKit in Objective-C geschrieben ist.



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
    }
}
      
      





Erweitern wir nun das Modell mit dem ListDiffable- Protokoll .



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
    }
}
      
      





Mit ListDiffable können Sie Objekte eindeutig identifizieren und vergleichen, um Daten in der UICollectionView automatisch und fehlerfrei zu aktualisieren.



Das Protokoll erfordert die Implementierung von zwei Methoden:



func diffIdentifier() -> NSObjectProtocol
      
      







Diese Methode gibt eine eindeutige Kennung für das zum Vergleich verwendete Modell zurück.



func isEqual(toDiffableObject object: ListDiffable?) -> Bool
      
      







Diese Methode wird verwendet, um zwei Modelle miteinander zu vergleichen.



Bei der Arbeit mit IGListKit werden häufig Modelle verwendet, um die einzelnen Zellen und den SectionController zu erstellen und zu betreiben. Diese Modelle werden nach den oben beschriebenen Regeln erstellt. Ein Beispiel kann im Repository angezeigt werden .



Synchronisieren einer Zelle mit einem Datenmodell



Nach dem Erstellen des Zellenmodells müssen Sie die Daten mit dem Füllen der Zelle selbst synchronisieren. Nehmen wir an, wir haben bereits eine erweiterte ExpandingCell . Fügen wir die Möglichkeit hinzu, mit IGListKit zu arbeiten, und erweitern Sie es, um mit dem ListBindable- Protokoll zu arbeiten .



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")
    }
}

      
      





Dieses Protokoll erfordert die Implementierung der Methode func bindViewModel (_ viewModel: Any) . Diese Methode aktualisiert die Daten in der Zelle.



Erstellen einer Zellenliste - SectionController



Nachdem wir Datenmodelle und Zellen fertiggestellt haben, können wir sie verwenden und eine Liste erstellen. Erstellen wir eine SectionController-Klasse.



final class InfoSectionController: ListBindingSectionController<ListDiffable> {
 
    weak var delegate: InfoSectionControllerDelegate?
    
    override init() {
        super.init()
        
        dataSource = self
    }
}
      
      





Unsere Klasse erbt von
ListBindingSectionController<ListDiffable>
      
      





Dies bedeutet, dass jedes Modell, das ListDiffable entspricht, mit SectionController funktioniert.



Wir müssen auch das SectionController-Protokoll ListBindingSectionControllerDataSource erweitern .



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)
    }
}
      
      







Um das Protokoll einzuhalten, implementieren wir drei Methoden:



func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, viewModelsFor object: Any) -> [ListDiffable] 

      
      





Diese Methode bildet ein Array von Modellen in der Reihenfolge, in der sie in der UICollectionView angezeigt werden.



func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, cellForViewModel viewModel: Any, at index: Int) -> UICollectionViewCell & ListBindable 

      
      





Die Methode gibt die gewünschte Zelle gemäß dem Datenmodell zurück. In diesem Beispiel wird der Code zum Verbinden der Zelle separat entfernt. Weitere Informationen finden Sie im Repository .



func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, sizeForViewModel viewModel: Any, at index: Int) -> CGSize 

      
      





Die Methode gibt die Größe für jede Zelle zurück.



Einrichten des ViewControllers



Verbinden wir den ListAdapter und das Datenmodell mit dem vorhandenen ViewController und füllen ihn ebenfalls aus. Mit ListAdapter können Sie UICollectionView mit Zellen erstellen und aktualisieren.



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
    }
}
      
      







Um ordnungsgemäß zu funktionieren, muss der Adapter das ViewController-Protokoll ListAdapterDataSource erweitern .



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
    }
}
      
      





Das Protokoll implementiert 3 Methoden:



func objects(for listAdapter: ListAdapter) -> [ListDiffable]
      
      







Die Methode erfordert die Rückgabe eines Arrays des gefüllten Modells für den SectionController.



func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController
      
      







Diese Methode initialisiert den benötigten SectionController.



func emptyView(for listAdapter: ListAdapter) -> UIView?
      
      







Gibt die Ansicht zurück, die angezeigt wird, wenn Zellen fehlen.



Hier können Sie das Projekt starten und die Arbeit überprüfen - die UICollectionView sollte gebildet werden. Da wir in unserem Artikel auf dynamische Listen eingegangen sind, werden wir auch die Behandlung für Zellklicks und die Anzeige einer verschachtelten Zelle hinzufügen.



Behandlung von Klickereignissen



Wir müssen den SectionController mit dem ListBindingSectionControllerSelectionDelegate-Protokoll erweitern und die Protokollkonformität im Initialisierer hinzufügen.



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)
    }
}
      
      







Die folgende Methode wird aufgerufen, wenn auf eine Zelle geklickt wird:



func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didSelectItemAt index: Int, viewModel: Any) 
      
      







Verwenden wir einen Delegaten, um das Datenmodell zu aktualisieren.



protocol InfoSectionControllerDelegate: class {
    func sectionControllerDidTapField(_ field: ExpandingCellModel)
}
      
      





Wir werden den ViewController erweitern und jetzt, wenn wir im Unternehmensdatenmodell auf die ExpandingCellModel- Zelle klicken , die isOpened- Eigenschaft ändern . Der Adapter aktualisiert dann den Status der UICollectionView, und die folgende Methode aus dem SectionController zeichnet die neu geöffnete Zelle:



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)
    }
}
      
      







Zusammenfassen



In diesem Artikel haben wir die Funktionen zum Erstellen einer dynamischen Sammlung mit IGListKit und Ereignisbehandlung untersucht. Obwohl wir nur einen Teil der möglichen Funktionen des Frameworks angesprochen haben, kann selbst dieser Teil für den Entwickler in den folgenden Situationen nützlich sein:



  • schnell flexible Listen zu erstellen;
  • die Erfassungslogik vom Haupt-ViewController zu kapseln und dadurch zu laden;
  • um eine Sammlung mit mehreren Arten von Zellen einzurichten und diese wiederzuverwenden.


! .



gif





All Articles