★★ 给我加星关注项目! ★★
由 Daniele Margutti 创建 - danielemargutti.com
FlowKit 能做什么
FlowKit 是一种新的方法,用于创建、填充和管理 UITableView
和 UICollectionView
。
通过声明性和类型安全的方法,您无需再实现数据源/代理:您的代码易于阅读、维护,且符合 SOLID 设计原则。
你想要了解更多关于 FlowKit 的信息吗?
我已经写了一篇入门文章:点击这里现在阅读!
主要特性
- 无需更多数据源/代理:您无需实现大量方法只为了渲染数据。只需简单易理解的函数来管理您想显示、删除或移动的数据。
- 类型安全:注册您想渲染的模型/单元类型对,然后以纯 Swift 类型安全方式访问它们的实例。
- 自动布局:自定尺寸的单元易于配置,适用于表格和收集视图。
- 内置自动动画:在数据源和表格/收集视图之间的更改与同步会自动进行评估,您可以免费获得动画效果。
- 紧凑代码:您的表格和收集视图代码易于阅读和维持;通过声明性方式在
add
/move
和remove
函数(都可针对部分、头部/脚部以及单个行)中对数据源进行更改。
您将得到什么
以下代码只是一个简单的示例,展示了如何使用FlowKit创建一个简单的联系人列表UITableView(它与集合的使用类似)
// Create a director which manage the table/collection
let director = TableDirector(self.tableView)
// Declare an adapter which renders Contact Model using ContactCell
let cAdapter = TableAdapter<Contact,ContactCell>()
// Hook events you need
// ...dequeue
cAdapter.on.dequeue = { ctx in
self.fullNameLabel.text = "\(ctx.model.firstName) \(ctx.model.lastName)"
self.imageView.image = loadFromURL(ctx.model.avatarURL)
}
// ...tap (or all the other events you typically have for tables/collections)
cAdapter.on.tap = { ctx in
openDetail(forContact: ctx.model)
}
// Register adapter; now the director know how to render your data
director.register(adapter: cAdapter)
// Manage your source by adding/removing/moving sections & rows
director.add(models: arrayOfContacts)
// Finally reload your table
director.reloadData()
很简单,对吧?
没有数据源,没有代理,只需要声明式语法来轻松且类型安全地创建和管理您的数据(对模型和单元格都适用)。通过阅读本指南的其余部分了解有关分区、头部/尾部和事件的更多信息。
目录
文档
以下指南通过实际示例解释了如何在FlowKit中使用功能。如果您想看到实时示例,请打开FlowKit.xcodeproj
并运行Example
应用程序。
注意:以下概念在对使用FlowKit的表格或集合(每个类都以Table[...]
或Collection[...]
前缀开头,并且函数之间有相似性时,函数/属性的名称是一致的)工作时同样有效。
概述
以下图表描述了FlowKit中集合的基础结构(此图表也可以在这里找到表格的相同图表)。
FlowKit中最重要的类是Director类;这个类(表格的TableDirector
,集合的CollectionDirector
/FlowCollectionDirector
)负责管理和数据以及UI之间的同步:您可以通过调用list.director
或在您的列表实例上创建自己的director来管理列表的外观和行为。使用FlowKit的第一步是为您的列表分配一个director:您可以通过调用list.director
或通过使用您要管理的列表实例创建自己的director来实现。
let director = FlowCollectionDirector(self.collectionView)
为了将数据渲染到列表中,FlowKit 必须知道您想展示哪种数据;数据以<Model,View>
对的组织方式(其中Model
是您想添加到表中的对象,View 是用来表示数据的单元格)进行组织。 Model
必须是一个对象(类或结构体),并且需要符合 ModelProtocol
协议:这是一个简单的协议,需要存在一个 modelID
属性。这个属性用来唯一识别模型,并且在自动刷新时用动画来评估项目之间的差异。
适配器还允许接收用于配置视图和行为的事件:您可以拦截您的模型实例的点击事件并执行某些操作,或者只需将接收到的类型安全的单元格实例填充为模型实例。
因此,第二步,您需要注册一些适配器
let adapter = CollectionAdapter<Contact,ContactCell>()
adapter.on.tap = { ctx in
print("User tapped on \(ctx.model.firstName)")
}
adapter.on.dequeue = { ctx in
ctx.cell?.titleLabel?.text = ctx.model.firstName
}
现在您已经准备好使用模型创建部分了
let section = TableSection(headerTitle: "Contacts", models: contactsList)
self.tableView.director.add(section: section)
模型数组可以是异构的,但请记住让您的对象符合 ModelProtocol
和 Hashable
协议,并注册相关适配器。FlowKit 将根据需要调用您的适配器事件。
最后您可以重新加载数据
self.tableView.reloadData()
太棒了!只需几行代码,您就已经创建并管理了复杂的列表。
以下指南描述了库的所有其他功能。
创建导演(TableDirector
/CollectionDirector
)
您可以认为导演是表格/集合的所有者/管理者:使用它可以声明您的滚动器可以显示哪些类型的数据(模型和视图/单元格),添加/删除/移动部分和部分中的项目。从您开始使用 FlowKit 开始,您将使用导演实例来管理 UI 的内容。
请记住:单个导演实例只能管理一个滚动器实例。
您有两种方式设置导演;明确地
public class ViewController: UIViewController {
@IBOutlet public var tableView: UITableView?
private var director: TableDirector?
override func viewDidLoad() {
super.viewDidLoad()
self.director = TableDirector(self.tableView!)
}
}
或者通过访问 director
属性隐性设置:第一次调用时,将创建一个新的导演实例,并以强引用分配给表格/集合实例。
注意:对于 UICollectionView
,自动创建 FlowCollectionDirector
;如果您使用其他布局,则必须手动创建一个新的。
let director = self.tableView.director // create a director automatically and assign as strong reference to the table/collection
// do something with it...
注册适配器(TableAdapter
/CollectionAdapter
)
一旦你有了导演,你就需要告诉他你将要渲染哪种类型的数据:你可以在你的滚动器中有一个模型和视图(单元格)的异构集合,但一个单一的模型可以被渲染到单个类型的视图中。
假设您想渲染两种类型的模型
Contact
实例使用ContactCell
视图ContactGroup
实例使用ContactGroupCell
视图
您需要两个适配器
let contactAdpt = TableAdapter<Contact,ContactGroup>()
let groupAdpt = TableAdapter<ContactGroup,ContactGroupCell>()
tableView.director.register(adapters: [contactAdpt, groupAdpt])
现在您可以准备展示您的数据了。
创建数据模型(ModelProtocol
)
为了渲染数据,每个滚动对象都必须遵守ModelProtocol
协议,这是一个简单的协议,要求实现modelID
属性(一个Int
)。这个属性用于唯一标识模型,并在动画自动重新加载期间评估项目之间的差异。对于基于类的对象(AnyObject
)而言,该属性有一个默认实现,使用ObjectIdentifier
。对于基于值的对象(例如结构体),必须提供一个显式的实现。
以下是一个Contact
模型的实现示例。
public class Contact: ModelProtocol {
public var name: String
public var GUID: String = NSUUID().uuidString
public var id: Int {
return GUID.hashValue
}
public static func == (lhs: Contact, rhs: Contact) -> Bool {
return lhs.GUID == rhs.GUID
}
public init(_ name: String) {
self.name = name
}
}
创建单元格(UITableViewCell
/UICollectionViewCell
)
UITableViewCell
和UICollectionViewCell
及其子类自动遵守CellProtocol
协议。
唯一的约束是关于reuseIdentifier
:单元格必须有reuseIdentifier
(Interface Builder中的Identifier
)此类自身的名称。
如果需要,可以通过覆盖单元格的reuseIdentifier:
String
属性并返回您自己的标识符来覆盖此行为。
单元格可以通过三种方式加载
- 从Storyboard中的单元格:这是默认行为;您不需要做任何事情,单元格会自动注册。
- 从XIB文件中的单元格:确保您的xib文件与类的名称相同(例如
ContactCell.xib
),并且单元格是根项。 - 从
initWithFrame
中加载单元格:覆盖CellProtocol
的registerAsClass
以返回true
。
以下是一个ContactCell
的小示例。
public class ContactCell: UITableViewCell {
@IBOutlet public var labelName: UILabel?
@IBOutlet public var labelSurname: UILabel?
@IBOutlet public var icon: UIImageView?
}
添加分区(TableSection
/CollectionSection
)
每个表/集合至少需要有一个分区来显示内容。TableSection
/CollectionSection
实例包含要显示的项目(在.models
属性中),以及可选的任何可以应用的头/尾。
为了管理表的分区,您需要使用以下Director父类的方法
set(models:)
改变表的集合的分区。返回索引处的分区。
firstSection()
如果有的话,返回第一个分区。lastSection()
如果有的话,返回最后一个分区。add(section:at:)
添加或插入一个新分区。add(sections:at:)
添加一个分区数组。add(models:)
创建一个包含给定模型的新分区,并添加它。removeAll(keepingCapacity:)
删除所有分区。remove(section:)
删除指定索引的分区。remove(sectionsAt:)
删除指定索引集合的分区。move(swappingAt:with:)
在指定索引和目标索引之间交换分区。move(from:to)
指定索引到目标索引的组合删除/插入分区。
以下示例创建一个包含一些项的新 TableSection
,具有基于 String
的标题,然后将其附加到表格末尾。
let section = TableSection(headerTitle: "The Strangers", items: [mrBrown,mrGreen,mrWhite])
table.director.add(section: section)
管理分区中的模型/项
对于分区,同样有可供使用的 add
/remove
/move
函数来操作描述每个部分内容(行/项)的 models
数组(TableSection
/CollectionSection
实例)。
这是完整列表
set(models:)
修改模型数组。add(model:at:)
在指定索引处添加模型,如果为 nil,则附加到底部。add(models:at:)
从指定索引开始添加模型;如果为 nil,则附加到底部。remove(at:)
删除指定索引处的模型。remove(atIndexes:)
删除指定索引集合的模型。removeAll(keepingCapacity:)
删除分区的所有模型。move(swappingAt:with:)
将指定索引处的模型交换到另一个目标索引。move(from:to:)
删除指定索引处的模型并将其插入到目标索引。
在进行了任何更改后,您必须从主线程调用 director.reloadData()
函数来更新 UI。
下面是一个管理项的示例
let section = self.tableView.director.firstSection()
let newItems = [Contact("Daniele","Margutti"),Contact("Fabio","Rossi")]
section.add(models: newItems) // add two new contacts
section.remove(at: 0) // remove first item
// ...
self.tableView.director.reloadData() // reload data
设置头部和底部(TableSectionView
/CollectionSectionView
)
简单的头部/尾部
分区可以有也可以没有头部/尾部;这些可以是简单的 String
(如上所示)或自定义视图。
设置简单头部非常直接,只需设置 headerTitle
/footerTitle
section.headerTitle = "New Strangers"
section.footerTitle = "\(contacts.count) contacts")
自定义视图头部/尾部
要将自定义视图用作头部/尾部,您需要创建一个具有 UITableViewHeaderFooterView
(适用于表)或 UICollectionReusableView
(适用于集合)视图子类的自定义 xib
文件作为根项。
以下示例演示了自定义头部以及如何设置它
// we also need of TableExampleHeaderView.xib file
// with TableExampleHeaderView view as root item
public class TableExampleHeaderView: UITableViewHeaderFooterView {
@IBOutlet public var titleLabel: UILabel?
}
// create the header container (will receive events)
let header = TableSectionView<TableExampleHeaderView>()
// hooks any event. You need at least the `height` as below
header.on.height = { _ in
return 150
}
// Use it
let section = TableSection(headerView: header, items: [mrBrown,mrGreen,mrWhite])
table.director.add(section: section)
无动画重新加载数据
每次对数据模型的更改都必须通过调用位于章节和项目级别的add
/remove
/move
函数来完成。更改后,您需要调用导演的reloadData()
函数来更新UI。
以下示例显示在某些更改后更新表格。
// do some changes
tableView.director.remove(section: 0)
tableView.director.add(section: newSection, at: 2)
...
tableView.director.firstSection().remove(at: 0)
// then reload
tableView.director.reloadData()
如果您需要执行动画重载,只需在方法的可用回调中更改模型。动画将被评估并自动应用!
tableView.director.reloadData(after: { _ in
tableView.director.remove(section: 0)
tableView.director.add(section: newSection, at: 2)
return TableReloadAnimations.default()
})
对于TableDirector
,您必须提供一个TableReloadAnimations
配置,该配置定义了对每个类型的更改(插入/删除/重载/移动)必须应用哪种UITableViewRowAnimation
。TableReloadAnimations.default()
仅为每种类型使用.automatic
(您还可以实现自己的对象,该对象需要符合TableReloadAnimationProtocol
协议)。
对于CollectionDirector
,您不需要返回任何内容;将基于使用的布局进行评估。
监听事件
所有事件都可以从各自的.on
属性开始钩取。所有标准表格与集合事件都可通过FlowKit获得;事件的名称与UIKit中对应的名称类似(请查阅官方文档了解有关任何具体事件的更多信息)。
单元格大小
FlowKit支持使用自动布局轻松设置单元格大小。您可以通过适配器或集合设置单元格的大小。对于由自动布局驱动的单元格大小设置,将rowHeight
(对于TableDirector
)或itemSize
(对于CollectionDirector
/FlowCollectionDirector
)设置为autoLayout
值,然后提供一个估算值。
接受值为:
default
:您必须提供单元格的高度(表格)或大小(集合)autoLayout
:使用自动布局评估单元格的高度;对于集合视图,您还可以通过在单元格实例中重写preferredLayoutAttributesFitting()
函数来提供自己的计算。fixed
:为所有单元格类型提供固定高度(如果您计划所有单元格大小相同,则更快)
安装
通过CocoaPods安装
CocoaPods 是一个依赖关系管理器,它自动化并简化了在你的项目中使用 FlowKit 等第三方库的过程。你可以使用以下命令安装它
$ sudo gem install cocoapods
需要 CocoaPods 1.0.1+ 版本才能构建 FlowKit。
通过 Podfile 安装
要使用 CocoaPods 将 FlowKit 集成到你的 Xcode 项目中,请在你的 Podfile
中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'TargetName' do
use_frameworks!
pod 'FlowKitManager'
end
然后,运行以下命令
$ pod install
Carthage
Carthage 是一个去中心化的依赖关系管理器,它构建你的依赖并为你提供二进制框架。
你可以使用 Homebrew 通过以下命令安装 Carthage
$ brew update
$ brew install carthage
要使用 Carthage 将 FlowKit 集成到你的 Xcode 项目中,请在你的 Cartfile
中指定它
github "malcommac/FlowKitManager"
运行 carthage
来构建框架,并将构建好的 FlowKit.framework
拖入你的 Xcode 项目。
需求
FlowKit 与 Swift 4.x 兼容。
- iOS 8.0+