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