柔軟なリストを作成する方法:動的UICollectionViewの概要-IGListKit

コレクションは多くのモバイルアプリケーションで利用できます。たとえば、ソーシャルネットワーク上の出版物のリスト、レシピ、フィードバックフォームなどがあります。 UICollectionViewは、それらを作成するためによく使用されます。柔軟なリストを作成するには、データモデルとビューを同期する必要がありますが、さまざまな障害が発生する可能性があります。



この記事では、IGListKitフレームワークについて検討し ます。上記の問題を解決するためにInstagram開発チームによって作成されました。これにより、いくつかのタイプのセルでコレクションを設定し、それらを数行で再利用できます。同時に、開発者はメインのViewControllerからフレームワークのロジックをカプセル化することができます。次に、動的コレクションの作成とイベントの処理の詳細について説明します。このレビューは、新しいツールを習得したい初心者と経験豊富な開発者の両方に役立ちます。







IGListKitの操​​作方法



IGListKitフレームワークの使用は、標準のUICollectionView実装とほぼ同じです。さらに、次のものがあります。



  • データ・モデル;
  • ViewController;
  • UICollectionViewCellコレクションのセル。


さらに、ヘルパークラスがあります。



  • SectionController-現在のセクションのセルの構成を担当します。
  • SectionControllerModel-各セクションには独自のデータモデルがあります。
  • UICollectionViewCellModel-セルごとに、独自のデータモデルもあります。


それらの使用についてさらに詳しく考えてみましょう。



データモデルの作成



まず、構造ではなくクラスであるモデルを作成する必要があります。この機能は、IGListKitが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
    }
}
      
      





次に、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を使用すると、オブジェクトを一意に識別して比較し、UICollectionView内のデータをエラーなしで自動的に更新できます。



このプロトコルでは、次の2つの方法を実装する必要があります。



func diffIdentifier() -> NSObjectProtocol
      
      







このメソッドは、比較に使用されるモデルの一意の識別子を返します。



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







この方法は、2つのモデルを相互に比較するために使用されます。



IGListKitを使用する場合、モデルを使用して各セルとSectionControllerを作成および操作するのが一般的です。これらのモデルは、上記のルールに従って作成されます。例はリポジトリで表示でき ます



セルとデータモデルの同期



セルモデルを作成した後、データをセル自体の入力と同期させる必要があります。すでにExpandingCellがレイアウトされているとしましょう IGListKitを操作する機能を追加し、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")
    }
}

      
      





このプロトコルには、func bindViewModel(_ viewModel:Any)メソッドの実装が必要 です。このメソッドは、セル内のデータを更新します。



セルのリストの作成-SectionController



データモデルとセルの準備ができたら、それらの使用とリストの作成を開始できます。SectionControllerクラスを作成しましょう。



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





私たちのクラスはから継承します
ListBindingSectionController<ListDiffable>
      
      





これは、ListDiffableに準拠するすべてのモデルがSectionControllerで機能することを意味します。



また、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)
    }
}
      
      







プロトコルに準拠するために、次の3つの方法を実装します。



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

      
      





このメソッドは、UICollectionViewに表示される順序でモデルの配列を作成します。



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

      
      





このメソッドは、データモデルに従って目的のセルを返します。この例では、セルを接続するためのコードが個別に取り出されています詳細については、リポジトリを参照してください



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

      
      





このメソッドは、各セルのサイズを返します。



ViewControllerの設定



ListAdapterとデータモデルを既存のViewControllerに接続し、それを埋めましょう。ListAdapterを使用すると、セルを使用してUICollectionViewを作成および更新できます。



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







正しく機能するには、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
    }
}
      
      





プロトコルは3つの方法を実装します:



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







このメソッドでは、SectionControllerの塗りつぶされたモデルの配列を返す必要があります。



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







このメソッドは、必要なSectionControllerを初期化します。



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







セルが欠落しているときに表示されるビューを返します。



これで、プロジェクトを開始して作業を確認できます。UICollectionViewが生成されます。また、この記事では動的リストに触れたので、セルクリックの処理とネストされたセルの表示を追加します。



クリックイベントの処理



ListBindingSectionControllerSelectionDelegateプロトコルを使用してSectionControllerを拡張し、イニシャライザーにプロトコルコンプライアンスを追加する必要があります。



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







セルがクリックされると、次のメソッドが呼び出されます。



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







デリゲートを使用してデータモデルを更新しましょう。



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





ViewControllerを拡張し、CompanyデータモデルのExpandingCellModelセルをクリックする isOpenedプロパティ変更します 次に、アダプターはUICollectionViewの状態を更新し、SectionControllerからの次のメソッドが新しく開いたセルを描画します。



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







まとめ



この記事では、IGListKitとイベント処理を使用して動的コレクションを作成する機能について説明しました。フレームワークの可能な機能の一部に触れただけですが、この部分でさえ、次の状況で開発者に役立つ可能性があります。



  • 柔軟なリストをすばやく作成する。
  • メインのViewControllerからコレクションロジックをカプセル化し、それをロードします。
  • 複数の種類のセルでコレクションを設定し、それらを再利用します。


! .



gif





All Articles