varentry: JournalEntry!letsolFormatter = SolFormatter()本文翻译自 Ryan Nystrom, IGListKit Tutorial: Better UICollectionViews.
大部分App都以相同的方式开始: 一些视图, 几个按钮, 一到两个列表. 但是随着时间的推移,app也在慢慢成长, 很多特性在以各自的方式蜕变. 在最后期限和产品经理的压力下, 也许你会对着你空白的数据源感到崩溃, 为了完成任务, 最后留下大量的需要维护的控制器视图. 快点开始采用IGListKit来减轻你的负担吧
使用IGListKit 有效避免了使用UICollectionView的产生的大量试图控制器,通过IGListKit创建列表,你可以使用解耦组件构建APP,快速迭代,并支持任何类型的数据类型
在这个例子中, 我们将基于UICollectionView以使用IGListKit,然后扩展该应用程序最终完成
开始
您是美国航空航天局顶尖的软件工程师之一,也是最新的火星任务。 该团队已经构建了Marslink应用程序的第一个版本,您可以在这里下载。 项目下载后,打开Marslink.xcworkspace并运行应用程序。
目前为止,应用程序只显示一份宇航员日列表。
只船员需要新的功能,您都需要为此应用添加功能。通过打开Marslink \ ViewControllerers \ ClassicFeedViewController.swift 熟悉项目。如果你曾经使用过UICollectionView,你看起来非常标准:
ClassicFeedViewController是一个UIViewController子类,在扩展中实现UICollectionViewDataSource。
viewDidLoad()创建一个UICollectionView,注册单元格,设置数据源,并将其添加到视图层次结构中。
loader.entries数组决定了section的数量,每个部分只有两个单元格(一个用于日期,一个用于文本)。
日期单元格使用日志文本配置Sol日期和文本输入单元格。
collectionView(_:layout:sizeForItemAt :)为日期单元格返回固定大小,并计算实际条目的文本大小。
一切似乎工作正常,但产品经理带有一些紧急的产品更新请求:
一名宇航员刚刚被困在火星上。 我们需要您添加一个天气模块和实时聊天。 你有48个小时。
来自JPL的工程师提供了一些模块,但是他们需要你帮助把它们放在应用程序中。
如果把宇航员带回家的所有压力都不够,美国航空航天局的首席设计师只是向你们传递了每个子系统在应用程序中的更新必须被动画化的要求,这意味着没有reloadData()。
怎么才能把所有的组件加入程序中, 并且让所有的转化有动画呢?
介绍
虽然UICollectionView是一个非常强大的工具,具有强大的组件和库。 保持数据源和视图同步是非常重要的,崩溃通常是由于断开连接造成的。
IGListKit是由Instagram在Instagram创建的数据驱动的UICollectionView框架。 通过这个框架,您可以提供一系列在UICollectionView中显示的对象。 对于每种类型的对象,适配器创建一个名为section controller的内容,该控件具有创建单元格的所有细节。
IGListKit会自动分辨对象,并在UICollectionView上执行动画批量更新以进行任何更改。 这样你就不必自己编写批量更新,避免在这里列出的问题。
使用
IGListKit能很好的识别集合中的更改以及使用动画更新相应行。它还可以轻松处理具有不同数据和UI的多个部分。考虑到这一点,它是对新批次的要求的完美解决方案 - 现在开始实现他!
Marslink.xcworkspace仍然打开,右键单击ViewControllers组,然后选择新建文件...。添加一个新的Cocoa Touch Class,它将UIViewController命名为FeedViewController。
打开AppDelegate.swift并找到应用程序(_:didFinishLaunchingWithOptions :)方法。找到将ClassicFeedViewController()推送到导航控制器并将其替换为的行:
nav.pushViewController(FeedViewController(),animated:false)
FeedViewController现在是根视图控制器。您将继续使用ClassicFeedViewController.swift进行参考,但是FeedViewController将用于实现新的IGListKit强制集合视图。
构建并运行并确保一个新的,空的视图控制器显示在屏幕上。
添加日记
打开FeedViewController.swift并将以下属性添加到FeedViewController的顶部:
let loader = JournalEntryLoader()
JournalEntryLoader是一个将硬编码的日记帐分录加载到条目数组中的类。
将以下内容添加到viewDidLoad()的底部:
loader.loadLatest()
loadLatest()是加载最新日记帐分录的JournalEntryLoader方法。
添加 collectionView
现在是开始向视图控制器添加一些IGListKit特定控件的时候了。在你做之前,你需要导入框架。在FeedViewController.swift顶部附近添加一个新的导入:
导入IGListKit
注意:本教程中的项目使用CocoaPods来管理依赖关系。 IGListKit是在Objective-C中编写的,所以如果您手动将其添加到项目中,则需要#import到您的桥接头
将初始化的collectionView常量添加到FeedViewController的顶部:
let collectionView: IGListCollectionView = {
let view = IGListCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
view.backgroundColor = UIColor.black
return view
}()
IGListKit使用IGListCollectionView,它是UICollectionView的一个子类,它优化一些功能并阻止其他功能。
这是从零大小的rect开始,因为视图尚未创建。它正如ClassicFeedViewController一样使用UICollectionViewFlowLayout。
背景颜色设置为NASA批准的黑色。
将以下内容添加到viewDidLoad()的底部:
view.addSubview(CollectionView)
这将新的collectionView添加到控制器的视图。
viewDidLoad()下面添加以下内容:
override func viewDidLayoutSubviews(){
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
viewDidLayoutSubviews()被覆盖,设置collectionView框架以匹配视图边界。
IGListAdapter and data source
使用UICollectionView,需要采用UICollectionViewDataSource的某种数据源。 它的工作是返回部分和行数以及单个单元格。
在IGListKit中,可以使用所谓的IGListAdapter来控制集合视图。 我们仍然需要符合IGListAdapterDataSource协议的数据源,而不是返回计数和单元格,您可以提供数组和段控制器(稍后再提供)。
对于初学者,在FeedViewController.swift中,在FeedViewController的顶部添加以下内容:
lazyvaradapter:IGListAdapter= {returnIGListAdapter(updater:IGListAdapterUpdater(), viewController:self, workingRangeSize:0)}()
这将为IGListAdapter创建一个延迟初始化的变量。 初始化器需要三个参数:
updater是符合IGListUpdatingDelegate的对象,它处理行和部分更新。 IGListAdapterUpdater是适合使用的默认实现。
viewController是容纳适配器的UIViewController。 此视图控制器稍后用于导航到其他视图控制器。
workingRangeSize是工作范围的大小,它允许您为可见框架之外的部分准备内容。
注意:工作范围是本教程未涉及的更高级的主题。 但是,IGListKit的备份还有很多文档甚至是一个示例应用程序!
将以下内容添加到viewDidLoad()的底部:
adapter.collectionView = collectionView
adapter.dataSource =self
这是将collectionView连接到适配器。 它还将self设置为适配器的dataSource,这会导致编译器错误,因为您尚未采用IGListAdapterDataSource协议。
通过将FeedViewController扩展为采用IGListAdapterDataSource来解决此问题。 将以下内容添加到文件的底部:
extension FeedViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return loader.entries
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return IGListSectionController()}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
FeedViewController现在遵守IGListAdapterDataSource并实现其三个必需的方法:
objects(for:)返回应该在集合视图中显示的数据对象数组。 这里提供了loader.entries,因为它包含日记帐分录。
对于每个数据对象,listAdapter(_:sectionControllerFor :)必须返回一个节控制器的新实例。 现在,您正在返回一个简单的IGListSectionController来安抚编译器 - 稍后,您将修改此命令以返回自定义日志部分控制器。
emptyView(for :)返回当列表为空时应显示的视图。 美国宇航局处于一个时间紧张状态,所以他们没有预算这个功能。
创建第一个小组控制器
给定一个数据对象节控制器这个抽象概念,它可以在collection view的一部分中配置和控制单元格。 这个概念类似于配置视图的view-model:数据对象是view-model,单元格是视图。 小组控制器作为两者之间的粘合剂。
在IGListKit中,您可以为不同类型的数据和行为创建一个新的小组控制器。 JPL工程师已经建立了一个JournalEntry模型,所以你需要创建一个可以处理它的小组控制器。
右键单击SectionControllers组,然后选择新建文件...。 创建一个新的Cocoa Touch类,名为JournalSectionController,继承IGListSectionController。
Xcode不会自动导入第三方框架,所以在JournalSectionController.swift中添加一行:
import IGListKit
添加一下属性
varentry: JournalEntry!
let solFormatter = SolFormatter()
JournalEntry是一个在实现数据源时将使用的模型类。 SolFormatter类提供了将日期转换为Sol格式的方法。 你很快就需要。
另外在JournalSectionController中,通过添加以下内容来覆盖init():
override init() {
super.init()
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
如果没有这样的话,各部分之间的单元格将彼此相邻。这将添加一个15点填充到JournalSectionController对象的底部。
您的小组控制器需要符合IGListSectionType协议,才能在IGListKit中使用。首先将以下扩展名添加到文件的底部:
extension JournalSectionController: IGListSectionType {
func numberOfItems() -> Int {
return 2
}
func sizeForItem(at index: Int) -> CGSize {return .zero}
func cellForItem(at index: Int) -> UICollectionViewCell {return UICollectionViewCell()}
func didUpdate(to object: Any) {}
func didSelectItem(at index: Int) {}
您已经实现了IGListSectionType协议的四个必需的方法。
numberOfItems默认返回2,分别对应日期和文本。 你可以参考一下ClassicFeedViewController.swift,collectionView(_:numberOfItemsInSection :)中也返回2, 这是一样的
在doUpdate(到:)中,添加以下内容:
entry =objectas? JournalEntry
didUpdate(to :)用于将对象传递给小组控制器。 注意,这种方法将始终在任何cell协议方法之前被调用。 在这里,保存传入的对象的数组
现在有了数据,开始配置cell。 在cellForItem(at :)中实现:
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
接下来,在 sizeForItem(at :) 中实现:
func sizeForItem(at index: Int) -> CGSize {// 1
guard let context = collectionContext, let entry = entry else { return .zero }
// 2
let width = context.containerSize.width
// 3
if index == 0 {
return CGSize(width: width, height: 30)
} else {
return JournalEntryCell.cellSize(width: width, text: entry.text)
}
}
现在你有一个控制器接收一个JournalEntry对象并返回和大小两个单元格。 现在是时候把它们整合在一起了。
返回到FeedViewController.swift中,将以下内容替换为listAdapter(_:sectionControllerFor :)的内容:
returnJournalSectionController()
现在编译一下, 应该可以看到和初始状态一样的样子了
后面就是相似的步骤了,添加了两种消息样式, 完成后的Demo, 点击这里
总结
IGListKit 的优点
1. 不要再次调用performBatchUpdates(_ :, completion :)或reloadData()
2. 更好的架构与可重复使用的单元和组件
3. 创建具有多种数据类型的Collections
4. 解耦差分算法
5. 完全单元测试
6. 定制您的模型的差异行为
7. 简单的UICollectionView是其核心
8. 可扩展API
9. 使用Objective-C语言,同时全面支持Swift