最近项目开发中使用到了开源库IGListKit
,所以在此记录下使用的心得体会,这会是IGListKit
一系列文章的第一篇(ps. 如果我没有鸽的话)。
这一篇主要简单讲下IGListKit
的使用方式,主要内容可以分以下三个部分:
- what - IGListKit是什么。
- why - 为什么使用IGListKit,介绍下IGListKit能解决什么问题
- 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)
}()
IGListKit
中有一个updater
的概念,它负责row和section的刷新,ListAdapterUpdater
是它的默认实现。viewController
对象,用来持有生成的adapter
workingRangeSize
的主要作用是设置工作区域大小,可以用来实现预加载的功能
创建成功adpater
后,需要对其进行设置collectionView
以及dataSource
,dataSource
对象需要实现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。