FlowKitManager 0.6.1

FlowKitManager 0.6.1

Daniele Margutti 维护。



FlowKit

Version License Platform CocoaPods Compatible Carthage Compatible Twitter

★★ 给我加星关注项目! ★★
Daniele Margutti 创建 - danielemargutti.com

FlowKit 能做什么

FlowKit 是一种新的方法,用于创建、填充和管理 UITableViewUICollectionView

通过声明性和类型安全的方法,您无需再实现数据源/代理:您的代码易于阅读、维护,且符合 SOLID 设计原则。

你想要了解更多关于 FlowKit 的信息吗?

我已经写了一篇入门文章:点击这里现在阅读!

主要特性

  • 无需更多数据源/代理:您无需实现大量方法只为了渲染数据。只需简单易理解的函数来管理您想显示、删除或移动的数据。
  • 类型安全:注册您想渲染的模型/单元类型对,然后以纯 Swift 类型安全方式访问它们的实例。
  • 自动布局:自定尺寸的单元易于配置,适用于表格和收集视图。
  • 内置自动动画:在数据源和表格/收集视图之间的更改与同步会自动进行评估,您可以免费获得动画效果。
  • 紧凑代码:您的表格和收集视图代码易于阅读和维持;通过声明性方式在 add/moveremove 函数(都可针对部分、头部/脚部以及单个行)中对数据源进行更改。

您将得到什么

以下代码只是一个简单的示例,展示了如何使用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)

模型数组可以是异构的,但请记住让您的对象符合 ModelProtocolHashable 协议,并注册相关适配器。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

UITableViewCellUICollectionViewCell及其子类自动遵守CellProtocol协议。

唯一的约束是关于reuseIdentifier单元格必须有reuseIdentifier(Interface Builder中的Identifier)此类自身的名称。

如果需要,可以通过覆盖单元格的reuseIdentifier: String属性并返回您自己的标识符来覆盖此行为。

单元格可以通过三种方式加载

  • 从Storyboard中的单元格:这是默认行为;您不需要做任何事情,单元格会自动注册。
  • 从XIB文件中的单元格:确保您的xib文件与类的名称相同(例如ContactCell.xib),并且单元格是根项。
  • initWithFrame中加载单元格:覆盖CellProtocolregisterAsClass以返回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配置,该配置定义了对每个类型的更改(插入/删除/重载/移动)必须应用哪种UITableViewRowAnimationTableReloadAnimations.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+