IGListKit学习系列 - 使用

7,724 阅读4分钟

最近项目开发中使用到了开源库IGListKit,所以在此记录下使用的心得体会,这会是IGListKit一系列文章的第一篇(ps. 如果我没有鸽的话)。

这一篇主要简单讲下IGListKit的使用方式,主要内容可以分以下三个部分:

  1. what - IGListKit是什么。
  2. why - 为什么使用IGListKit,介绍下IGListKit能解决什么问题
  3. How - 怎么使用IGListKit

一. IGListKit是什么

IGListKit是Instagram开源的第三方库。顾名思义,它是数据驱动的UICollectionView框架,用于构建快速灵活的列表。支持OC以及Swift。iOS接入需要符合以下条件:

  • Xcode 9.0 +
  • iOS 9.0+
  • Interoperability with Swift 3.0+

二. 为什么使用IGListKit

在我看来,使用IGListKit最大的优点,在于方便我们代码解耦合以及复用。想象一个场景,当我们开始创建一个app的时候,应用的UI可能很简单,或许只有一个列表,列表上显示的cell,可能只要简单展示一个图片或者按钮,这时候,你的数据源就会非常的简单干净,需要用到的model类似下面这种:


class commonModel {
    var imageUrl: String = ""
    var name: String = ""
}

但随着业务的快速发展,你的UI界面会变得更复杂,原先简单的显示图片或者按钮的cell,或许要添加评论,点赞等更复杂一些的功能。这时候,正常情况下,我们就会理所当然的在commonModel上添加更多的属性,以及相应的业务逻辑。对应的ViewController也会变得越来越臃肿。

为了解决这个问题,所以,它来了,IGListKit的使用原理可以简单的描述成下图这样子:

不同的数据,通过IGListKit的Adapter会创建对应sectionController,在sectionController上可以组合不同的cell,生成我们最终想要的视图,一个“大”的cell。

搬运一个详细的例子来说明下:

如上图所示的一个app,主要用途是用来记录天气、旅行日记、消息。显而易见,cell也可以分成以下三种:

这三个cell对应的数据源类型明显不一样,在IGListKit中就可以理解成三个sectionController。这样子划分我们就不需要把所有的数据都放在同一个model中,达到了解耦的效果。

每个sectionController可以控制不同的cell,来拼接成一个“大”的cell,以Journal Entries这个cell为例,可以划分成

  • 显示时间的cell
  • 显示正文内容的cell

cell的粒度越小,我们编写界面过程中,越方便进行cell的复用,按需拼接成最终需要展示的cell。

三. 怎么使用IGListKit

IGListKit的使用,从底层往上,可以分成三个步骤:

第一步 创建所需的细粒度cell

以上文中的旅行日记为例,我们就需要用到两个cell,一个显示时间的cell,一个显示正文内容的cell:

  • JournalEntryDateCell
  • JournalEntryCell

第二步 创建sectionController

创建完细粒度的cell后,我们就需要创建sectionController来管理它们,每个sectionController都继承ListSectionController,然后重写以下方法:

// MARK: - Data Provider
extension JournalSectionController {
  // 当前sectionController展示的cell数目,以上文中旅行日记为例,返回的应该是2
  override func numberOfItems() -> Int {
    return 2
  }
  // 返回相应cell的大小
  override func sizeForItem(at index: Int) -> CGSize {
    if index == 0 {
      return CGSize(width: width, height: 30)
    } else {
      return JournalEntryCell.cellSize(width: width, text: entry.text)
    }
  }
  
  // 根据下标,返回相应的cell
  override func cellForItem(at index: Int) -> UICollectionViewCell {
    let cellClass: AnyClass = index == 0 ? JournalEntryDateCell.self : JournalEntryCell.self
    let cell = collectionContext!.dequeueReusableCell(of: cellClass, for: self, at: index)
    
    if let cell = cell as? JournalEntryDateCell {
      cell.label.text = "SOL \(solFormatter.sols(fromDate: entry.date))"
    } else if let cell = cell as? JournalEntryCell {
      cell.label.text = entry.text
    }
    return cell
  }
  
  override func didUpdate(to object: Any) {
    entry = object as? JournalEntry
  }  
}

在上述方法中,didUpdate方法最先会被调用到,返回当前sectionController绑定的那个数据。

第三步,创建ListAdapter

最后这一步,我们需要在主的viewController(显示UIViewCollectionView的VC)中,创建下ListAdapter,在IGListKit中,我们使用它去控制collectionView:

lazy var adapter: ListAdapter = {
  return ListAdapter(
  updater: ListAdapterUpdater(),
  viewController: self, 
  workingRangeSize: 0)
}()

  1. IGListKit中有一个updater的概念,它负责row和section的刷新,ListAdapterUpdater是它的默认实现。
  2. viewController对象,用来持有生成的adapter
  3. workingRangeSize的主要作用是设置工作区域大小,可以用来实现预加载的功能

创建成功adpater后,需要对其进行设置collectionView以及dataSourcedataSource对象需要实现ListAdapterDataSource协议:

// MARK: - ListAdapterDataSource
extension ViewController: ListAdapterDataSource {
  // 1 返回数据数组
  func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
    return entries
  }
  
  // 2 根据不同的对象,生成不同的sectionController
  func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) 
  -> ListSectionController {
     if object is Message { // 如果是消息,则返回消息的sectionController
         return MessageSectionController()
     } else if object is jornal { // 如果是旅行日记,则返回旅行日记的sectionController
         return JournalSectionController()
     } else {
         return WeatherSectionController
     }
  }
  
  // 3 当没有数据的时候的空视图
  func emptyView(for listAdapter: ListAdapter) -> UIView? {
    return nil
  }
}

经过以上的三个步骤,你就可以简单的搭建起IGListKit的Demo。

四. 参考链接

IGListKit Tutorial: Better UICollectionViews

官方教程