![Eureka: 一个优雅的用Swift编写的表单生成框架](../Eureka.jpg)
由[XMARTLABS](http://xmartlabs.com)精心编写,是[XLForm]的Swift版本。
## 概览
## 目录
* [要求]
* [使用]
+ [如何创建表格]
+ [获取行的值]
+ [操作符]
+ [callbacks的使用]
+ [Section Header和Footer]
+ [动态地隐藏和显示row(或者Sections)]
+ [列表类型的sections]
+ [有多个值的sections]
+ [验证]
+ [滑动操作]
* [自定义row]
+ [简单的自定义rows]
+ [自定义内联rows]
+ [自定义Presenter rows]
* [row目录]
* [安装]
* [常见问题解答]
**想了解更多,可以查看我们的关于*Eureka*的[博客](https://blog.xmartlabs.com/2015/09/29/Introducing-Eureka-iOS-form-library-written-in-pure-Swift/)。**
## 要求
* Xcode 9.2+
* Swift 4+
### 示例程序
你可以clone这个项目,然后运行Example来欣赏Eureka的大部分特性。
|
|
## 使用
### 如何创建表格
通过继承 `FormViewController`,你可以很容易地把sections和rows添加到`form`变量。
```swift
import Eureka
class MyFormViewController: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
form +++ Section("Section1")
<<< TextRow(){ row in
row.title = "Text Row"
row.placeholder = "Enter text here"
}
<<< PhoneRow(){
$0.title = "Phone Row"
$0.placeholder = "And numbers here"
}
+++ Section("Section2")
<<< DateRow(){
$0.title = "Date Row"
$0.value = Date(timeIntervalSinceReferenceDate: 0)
}
}
}
```
在这个例子中,我们创建了两个包含基本rows的sections,效果如下图:
你也可以不继承`FormViewController`,然后自己设置`form`变量。但是通过继承的话,会方便很多。
#### 配置键盘导航辅助
如果需要改变导航辅助,你需要设置控制器的`navigationOptions`变量,是一个`OptionSet`类型,所以我们可以给它设置一个或者多个值:
- **disabled**: 不显示
- **enabled**: 显示在底部
- **stopDisabledRow**: 如果下一行被禁用了,就隐藏
- **skipCanNotBecomeFirstResponderRow**: 如果当前行的`canBecomeFirstResponder()`返回`false`,导航辅助就跳过这行
默认值是 `enabled & skipCanNotBecomeFirstResponderRow`
如果需要流畅的滚动屏幕,要把`animateScroll`设置为`true`。默认情况下,在导航辅助上点击上一个和下一个按钮的时候,`FormViewController`是直接跳到上一行或者下一行的,包括上一行或者下一行不在屏幕内的时候。
如果要设置键盘和正在编辑的row的间距,设置`rowKeyboardSpacing`即可。默认情况下,当表格滚动到一个没有显示出来的view,键盘的顶部和编辑行的底部是没有间距的。
```swift
class MyFormViewController: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
form = ...
// 开启导航辅助,并且遇到被禁用的行就隐藏导航
navigationOptions = RowNavigationOptions.Enabled.union(.StopDisabledRow)
// 开启流畅地滚动到之前没有显示出来的行
animateScroll = true
// 设置键盘顶部和正在编辑行底部的间距为20
rowKeyboardSpacing = 20
}
}
```
如果你想要改变导航辅助,需要在`FormViewController`的子类重写`navigationAccessoryView`。
### 获取行的值
`Row`对象有一个具体类型的 ***value*** 。
例如,`SwitchRow`有一个`Bool`,而`TextRow`有一个`String`。
```swift
// 获取单个row的值
let row: TextRow? = form.rowBy(tag: "MyRowTag")
let value = row.value
// 获取表格中所有rows的值(必须给每个row的tag赋值)
// 字典中包含的键值对为:['rowTag': value]。
let valuesDictionary = form.values()
```
### 操作符
Eureka包含了自定义的操作符,使得我们更容易地创建表格:
#### +++ 添加一个section
```swift
form +++ Section()
// 连起来添加多个sections
form +++ Section("First Section") +++ Section("Another Section")
// 或者直接利用行来创建一个空白的section
form +++ TextRow()
+++ TextRow() // 每个row显示在一个独立的section
```
#### <<< 插入一行
```swift
form +++ Section()
<<< TextRow()
<<< DateRow()
// 或者隐式地创建一个section
form +++ TextRow()
<<< DateRow()
```
#### += 添加一个数组
```swift
// 添加多个Sections到表格中
form += [Section("A"), Section("B"), Section("C")]
// 添加多个rows到一个section中
section += [TextRow(), DateRow()]
```
### callbacks的使用
Eureka包含了很多callbacks来更改行的外观和行为。
#### 理解`Row`和`Cell`
`Row`是抽象的,是Eureka用来存储 **value** 的并包含了一个`Cell`。这个`Cell`用来管理view,并继承自`UITableViewCell`。
例子:
```swift
let row = SwitchRow("SwitchRow") { row in // 初始化函数
row.title = "The title"
}.onChange { row in
row.title = (row.value ?? false) ? "The title expands when on" : "The title"
row.updateCell()
}.cellSetup { cell, row in
cell.backgroundColor = .lightGray
}.cellUpdate { cell, row in
cell.textLabel?.font = .italicSystemFont(ofSize: 18.0)
}
```
#### Callbacks清单
* **onChange()**
当row的`value`改变时调用。你可以在这里调整一些参数,甚至显示或隐藏其他row。
* **onCellSelection()**
当用户点击row并且被选中的时候调用。
* **cellSetup()**
当cell第一次配置的时候调用,并且仅调用一次。可以在这做一些永久性的设置。
* **cellUpdate()**
当cell每次在屏幕上显示的时候调用。可以在里个更新外观。
* **onCellHighlightChanged()**
当cell或者里面的subview 成为或者辞去第一响应者 时调用。
* **onRowValidationChanged()**
当与row关联的验证错误改变时调用。
* **onExpandInlineRow()**
内联row展开前调用。适用于遵循`InlineRowType`协议的rows。
* **onCollapseInlineRow()**
内联row折叠前调用。适用于遵循`InlineRowType`协议的rows。
* **onPresent()**
在显示另外一个view controller之前调用。适用于遵循`PresenterRowType`的rows。可以在这里设置被显示的view controller。
### Section Header和Footer
你可以将`String`的title或者自定义的`View`作为`Section`的header或者footer。
#### String title
```swift
Section("Title")
Section(header: "Title", footer: "Footer Title")
Section(footer: "Footer Title")
```
#### 自定义View
你可以使用一个`.xib`作为自定义View:
```swift
Section() { section in
var header = HeaderFooterView(.nibFile(name: "MyHeaderNibFile", bundle: nil))
// header每次出现在屏幕的时候调用
header.onSetupView = { view, _ in
// 通常是在这修改view里面的文字
// 不要在这修改view的大小或者层级关系
}
section.header = header
}
```
或者是一个使用纯代码创建的`UIView`
```swift
Section(){ section in
var header = HeaderFooterView(.class)
header.height = {100}
header.onSetupView = { view, _ in
view.backgroundColor = .red
}
section.header = header
}
```
或者直接用callback来创建view
```swift
Section(){ section in
section.header = {
var header = HeaderFooterView(.callback({
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .red
return view
}))
header.height = { 100 }
return header
}()
}
```
### 动态地隐藏和显示row(或者Sections)
在这个例子里,我们隐藏或显示整个sections。
为了达到这个效果,每个row有一个`Condition`的可选类型的变量`hidden`,`Condition`可以通过`function`或者`NSPredicate`来设置。
#### 使用function condition来隐藏
使用`Condition`的`function` case:
```swift
Condition.function([String], (Form)->Bool)
```
`function`需要一个`Form`参数,并返回`Bool`,决定当前row是否需要隐藏。这是一个非常强大的设置`hidden`的方式,因为它没有明显的限制。
```swift
form +++ Section()
<<< SwitchRow("switchRowTag"){
$0.title = "Show message"
}
<<< LabelRow(){
$0.hidden = Condition.function(["switchRowTag"], { form in
return !((form.rowBy(tag: "switchRowTag") as? SwitchRow)?.value ?? false)
})
$0.title = "Switch is on!"
}
```
```swift
public enum Condition {
case function([String], (Form)->Bool)
case predicate(NSPredicate)
}
```
#### 使用NSPredicate隐藏
`hidden`也可以用NSPredicate来设置。在predicate string里面,你可以引用其他row的tags,然后决定一个row是否需要隐藏。
但是使用NSPredicate来设置`hidden`的方式只适用于其他rows的value继承自NSObject(String 和 Int 也适用,因为它们被桥接到OjbC对应的类型,但是enums不适用)
使用NSPredicate限制这么多,我们为什么要使用它呢?
因为它比function更简单、更短而且更易读。例如下面这个例子:
```swift
$0.hidden = Condition.predicate(NSPredicate(format: "$switchTag == false"))
```
我们还可以写的更简单,因为`Condition`遵循`ExpressibleByStringLiteral`:
```swift
$0.hidden = "$switchTag == false"
```
*注意:我们会在执行的时候把'$switchTag'替换成tag为switchTag的row的value。*
所以的rows都必须有一个tag,我们会用这个tag去找到对应的row,这样才能达到我们想要的效果。
我们也可以直接隐藏一个row:
```swift
$0.hidden = true
```
因为`Condition`遵循`ExpressibleByBooleanLiteral`.
如果不设置`hidden`变量,那么对应的rows就会一直显示。
##### Sections
对于section来说,也是一样的。我们也可以通过设置`hidden`属性来控制显示或隐藏。
##### 禁用rows
为了禁用rows,每个row有一个`disable`变量,也是`Condition`的可选类型。使用的方式跟`hidden`一样,所以要求每个row有一个tag。
Note that if you want to disable a row permanently you can also set `disabled` variable to `true`.
注意:如果你想永久的禁用一个row,可以把`disabled`设置为`true`.
### 列表类型的sections
为了显示一个列表选项,Eukera有一个特殊的section,叫做`SelectableSection`。
当创建`SelectableSection`的时候,你需要传入在选项中使用的row的类型和`selectionTyle`。`selectionTyle`是一个枚举,`multipleSelection` 或者 `singleSelection(enableDeselection: Bool)`(`enableDeselection`决定选中的rows是否可以取消选中)。
```swift
form +++ SelectableSection>("Where do you live", selectionType: .singleSelection(enableDeselection: true))
let continents = ["Africa", "Antarctica", "Asia", "Australia", "Europe", "North America", "South America"]
for option in continents {
form.last! <<< ListCheckRow(option){ listRow in
listRow.title = option
listRow.selectableValue = option
listRow.value = nil
}
}
```
##### 可以使用什么类型的row?
为了创建这样的section,你必须先创建一个遵循`SelectableRowType`协议的row。
```swift
public protocol SelectableRowType : RowType {
var selectableValue : Value? { get set }
}
```
`selectableValue`是row的value,将会被永久存储。而`value`变量将会用来决定row是否选中,如果被选中,它的值就等于`selectableValue`,否则为nil。Eureka包含上面例子用到的`ListCheckRow`,在自定义rows的实例程序中,你还可以找到`ImageCheckRow`。
##### 获取选中的rows
为了获得`SelectableSection`中被选中的row,Eukera提供了两个方法:`selectedRow()`(获取`SingleSelection`选中的row) and `selectedRows()`获取`MultipleSelection`所有选中的rows。
##### 在sections中把选项分组
另外,你可以使用`SelectorViewController`的属性把选项列表分组。
- `sectionKeyForValue` - 是一个闭包,返回特定row的value对应的key,这个key被用来把选项分组。
- `sectionHeaderTitleForKey` - 是一个闭包,返回特定key对应的section的header title,默认是key本身的值。
- `sectionFooterTitleForKey` - 是一个闭包,返回特定key对应的section的footer title。
### 有多个值的sections
Eureka可以通过有多个值的section来支持一个字段对应多个值的情况。它允许我们很容易地创建能插入的、能删除的和能排序的sections。
#### 如何创建多值section
为了创建一个多值section,我们要使用`MultivaluedSection` ,而不是常规的`Section`。`MultivaluedSection`继承自`Section`,拥有一些额外的属性来设置多值section的行为。
让我们来看一个例子:
```swift
form +++
MultivaluedSection(multivaluedOptions: [.Reorder, .Insert, .Delete],
header: "Multivalued TextField",
footer: ".Insert adds a 'Add Item' (Add New Tag) button row as last cell.") {
$0.addButtonProvider = { section in
return ButtonRow(){
$0.title = "Add New Tag"
}
}
$0.multivaluedRowToInsertAt = { index in
return NameRow() {
$0.placeholder = "Tag Name"
}
}
$0 <<< NameRow() {
$0.placeholder = "Tag Name"
}
}
```
上面的代码演示了如何创建一个多值的section。在上面我们把insert, delete 和 reorder传给了`multivaluedOptions`。
`addButtonProvider`允许我们自定义button row,当点击这个button row并且`multivaluedOptions`包含`.Insert`的时候,就会添加一行。
`multivaluedRowToInsertAt`闭包在Eureka每次需要新的row插入的时候调用。为了提供一个row来插入到多值的section中,我们就要设置这个属性。Eureka传index作为闭包的参数。需要注意的是,我们可以返回任何类型的row,甚至自定义的row,即使在多数情况下多值section的rows都是同一类型的。
当我们创建多值section的时候,Eureka会自动添加button row。我们刚刚说到我们可以自定义button row的外观。默认情况下button row的左边有一个加号按钮,我们可以通过设置`showInsertIconInAddButton`属性来决定是否要显示加号按钮。
当创建一个可以插入的sections时,我们还需要更多的考虑。所以被添加到可插入的多值section的rows都应该放在Eureka通过点击按钮来插入的rows上面。我们可以在初始化section的时候,在最后一个闭包参数里添加额外的row,这样在点击button row的时候就会自动把要插入的rows放在最后面。
#### 编辑模式
默认情况下,Eureka只会在当表格中有含有MultivaluedSection的时候把tableView的`isEditing`设置为`true`,而且会在表格第一次显示时的`viewWillAppear`完成。
要了解更多如何使用多值section的相关信息,可以看下Eureka的示例程序,里面包含了多种用法。
### 验证
Eureka 2.0.0 内置了很多验证特性。
一个row有很多规则和一个用于决定rules是否需要验证的特定配置。
有很多规则是默认提供的,但是你也可以自己创建自己的规则。
默认提供的规则:
* RuleRequired
* RuleEmail
* RuleURL
* RuleGreaterThan, RuleGreaterOrEqualThan, RuleSmallerThan, RuleSmallerOrEqualThan
* RuleMinLength, RuleMaxLength
* RuleClosure
让我们看看如何设置验证规则。
```swift
override func viewDidLoad() {
super.viewDidLoad()
form
+++ Section(header: "Required Rule", footer: "Options: Validates on change")
<<< TextRow() {
$0.title = "Required Rule"
$0.add(rule: RuleRequired())
// 这也可以通过一个闭包来实现:如果验证通过,返回nil,否则返回一个ValidationError。
/*
let ruleRequiredViaClosure = RuleClosure { rowValue in
return (rowValue == nil || rowValue!.isEmpty) ? ValidationError(msg: "Field required!") : nil
}
$0.add(rule: ruleRequiredViaClosure)
*/
$0.validationOptions = .validatesOnChange
}
.cellUpdate { cell, row in
if !row.isValid {
cell.titleLabel?.textColor = .red
}
}
+++ Section(header: "Email Rule, Required Rule", footer: "Options: Validates on change after blurred")
<<< TextRow() {
$0.title = "Email Rule"
$0.add(rule: RuleRequired())
$0.add(rule: RuleEmail())
$0.validationOptions = .validatesOnChangeAfterBlurred
}
.cellUpdate { cell, row in
if !row.isValid {
cell.titleLabel?.textColor = .red
}
}
```
从上面的代码可以看到,我们可以通过 `add(rule:)`设置多个我们想要的规则。
Row还提供了`func remove(ruleWithIdentifier identifier: String)`来移除规则。为了使用这个方法,我们必须在创建规则的时候,给规则设置一个id。
有时候我们想要在这个row上使用的规则,跟其他rows是一样的。这种情况下,我们可以使用`RuleSet`来设置所以规则:
```swift
var rules = RuleSet()
rules.add(rule: RuleRequired())
rules.add(rule: RuleEmail())
let row = TextRow() {
$0.title = "Email Rule"
$0.add(ruleSet: rules)
$0.validationOptions = .validatesOnChangeAfterBlurred
}
```
Eureka允许我们指定何时执行验证规则。我们可以通过row的`validationOptions`属性来设置,它有以下这些值:
* `.validatesOnChange` - 当row的value改变时执行。
* `.validatesOnBlur` - (默认值)当cell辞去第一响应者时执行,不适用于所有rows。
* `.validatesOnChangeAfterBlurred` - 在第一次辞去第一响应者之后,row的value改变时执行
* `.validatesOnDemand` - 我们需要手动调用`validate()`来验证row或者form
如果你想验证整个form(或者所有rows),你可以手动调用Form的`validate()`方法。
#### 如何获取验证错误
每个row都有一个`validationErrors`属性,可以用来获取所有验证错误。这个属性仅仅存储了最后一次验证的错误清单,并不会执行验证规则。
#### 注意类型
正如我们所想的那样,规则的类型必须与row的类型相同。要格外小心检查所使用的row类型。你可能会看到这个编译错误:`"(Incorrect arugment label in call (have 'rule:' expected 'ruleSet:')"`,这并不是把类型混淆了的问题。
### 滑动操作
Eureka 4.1.0 引入了滑动特性.
现在你可以为每一行定义多个`leadingSwipe` 和 `trailingSwipe`操作。因为滑动操作决定于iOS系统的特性,所以`leadingSwipe`只能用于iOS 11 以上的系统。
让我们看看如何定义滑动操作:
```swift
let row = TextRow() {
let deleteAction = SwipeAction(
style: .destructive,
title: "Delete",
handler: { (action, row, completionHandler) in
// 在这添加你的代码
// 操作完成后一定要调用completionHandler
completionHandler?(true)
})
deleteAction.image = UIImage(named: "icon-trash")
$0.trailingSwipe.actions = [deleteAction]
$0.trailingSwipe.performsFirstActionWithFullSwipe = true
// 请注意:`leadingSwipe`只能用于iOS 11以上的系统
let infoAction = SwipeAction(
style: .normal,
title: "Info",
handler: { (action, row, completionHandler) in
// 在这添加你的代码
// 操作完成后一定要调用completionHandler
completionHandler?(true)
})
infoAction.backgroundColor = .blue
infoAction.image = UIImage(named: "icon-info")
$0.leadingSwipe.actions = [infoAction]
$0.leadingSwipe.performsFirstActionWithFullSwipe = true
}
```
滑动操作需要把`tableView.isEditing`设置为`false`。Eureka会在当表格中有含有MultivaluedSection的时候把tableView的`isEditing`设置为`true`(在`viewWillAppear`设置)。如果你在同一个表格中同时拥有MultivaluedSections和滑动操作,你要根据情况来设置`isEditing`。
## 自定义row
通常你需要自定义不同于Eureka内置的row。这其实不是很难,你可以阅读[如何自定义rows的教程](https://blog.xmartlabs.com/2016/09/06/Eureka-custom-row-tutorial/)来开始。你也可以看看[EurekaCommunity],这里包含了其他rows,并准备加入到Eukera中。
### 简单的自定义rows
为了创建一个拥有自定义行为和外观的row,你可能需要继承于`Row`和`Cell`。
请记住`Row`是Eureka使用的抽象类;而`Cell`实际上是`UITableViewCell`,用于管理view。
因为`Row`包含了`Cell`,所以`Row`和`Cell`必须同时定义。
```swift
// 自定义value类型是Bool的Cell
// Cell是使用 .xib 定义的,所以我们可以直接设置outlets
public class CustomCell: Cell, CellType {
@IBOutlet weak var switchControl: UISwitch!
@IBOutlet weak var label: UILabel!
public override func setup() {
super.setup()
switchControl.addTarget(self, action: #selector(CustomCell.switchValueChanged), for: .valueChanged)
}
func switchValueChanged(){
row.value = switchControl.on
row.updateCell() // Re-draws the cell which calls 'update' bellow
}
public override func update() {
super.update()
backgroundColor = (row.value ?? false) ? .white : .black
}
}
// 自定义的Row,拥有CustomCell和对应的value
public final class CustomRow: Row, RowType {
required public init(tag: String?) {
super.init(tag: tag)
// 我们把对应CustomCell的 .xib 加载到cellProvidor
cellProvider = CellProvider(nibName: "CustomCell")
}
}
```
结果:
自定义rows需要继承自 `Row`,并遵循`RowType`协议。
自定义的cells必须继承自`Cell`,并遵循`CellType`协议。
就像cellSetup和CellUpdate回调一样,`Cell`有setup和update方法,你可以在里面做自定义。
### 自定义内联rows
内联row是一个特定的row类型,可以在它下面动态的显示一个row。正常来说内联row在被点击时在展开和折叠两种状态切换。
所以,为了创建一个内联row,我们需要两个rows,一个总是显示的row,另外一个被展开和折叠的row。
另外一个要求是,这两个rows的value类型必须是一样的。
一旦我们拥有了两个rows,我们要让第一个row遵循`InlineRowType`,这将会给第一个row添加一些方法:
```swift
func expandInlineRow()
func hideInlineRow()
func toggleInlineRow()
```
最后,当row被点击时我们要调用`toggleInlineRow()`,例如重写`customDidSelect()`方法:
```swift
public override func customDidSelect() {
toggleInlineRow()
}
```
### 自定义Presenter rows
**注意:** *一个Presenter row 是可以弹出UIViewController的row。*
为了创建一个Presenter rows,必须创建一个遵循`PresenterRowType`的类。高度推荐继承自`SelectorRow`,因为它遵循了那个协议并且添加了其他很有用的方法。
`PresenterRowType`协议的定义如下:
```swift
public protocol PresenterRowType: TypedRowType {
typealias ProviderType : UIViewController, TypedRowControllerType
var presentationMode: PresentationMode? { get set }
var onPresentCallback: ((FormViewController, ProviderType)->())? { get set }
}
```
`onPresentCallback`将会在row即将显示另外一个view controller的时候调用。在`SelectorRow`里面已经调用了,如果你没有继承自它的话,你需要自己手动调用。
`presentationMode`定义了应该显示哪个controller和怎么显示controller。我们可以通过Segue identifier、segue class、present或者push来展示controller。例如一个CustomPushRow可以像这样定义:
```swift
public final class CustomPushRow: SelectorRow, SelectorViewController>, RowType {
public required init(tag: String?) {
super.init(tag: tag)
presentationMode = .show(controllerProvider: ControllerProvider.callback {
return SelectorViewController(){ _ in }
}, onDismiss: { vc in
_ = vc.navigationController?.popViewController(animated: true)
})
}
}
```
你可以用自己的UIViewController替换`SelectorViewController`,用自己的cell替换`PushSelectorCell`。
### 使用相同的row来子类化cells
有时候我们想要改变我们其中一个row的外观,但是不需要改变row的类型和已有的逻辑。如果我们使用的cell是通过`xib`文件来初始化的,那么现在有一种方法能达到我们的要求。目前,所有Eureka内置的rows都不是通过`xib`来初始化的,但是[EurekaCommunity]的一些自定义rows是通过`xib`初始化,例如[PostalAddressRow](https://github.com/EurekaCommunity/PostalAddressRow)。
你所需要做的是:
* 创建一个包含你想要创建的cell的nib文件。
* 然后把cell的class设置为你想要修改的cell(如果你想要更改除了UI以外的东西,你需要子类化那个cell。),并且确保那个cell的module是正确的。
* 把outlets连接到类中
* 告诉你的row使用这个新的nib文件。这是通过设置`cellProvider`来完成的,这个设置需要在具体的初始化过程或者`defaultRowInitializer`里面完成。例如:
```swift
<<< PostalAddressRow() {
$0.cellProvider = CellProvider(nibName: "CustomNib", bundle: Bundle.main)
}
```
另外,你也可以创建一个新的row来达到目的。这种情况下你要继承同一个父类,让这个row继承它的逻辑。
当我们在实现这个的时候,有些东西可以考虑下:
* 如果你想要看例子,你可以看看[PostalAddressRow](https://github.com/EurekaCommunity/PostalAddressRow)或者[CreditCardRow](https://github.com/EurekaCommunity/CreditCardRow),它们都是用了自定义的nib文件。
* 如果你遇到了这样的错误:`Unknown class in Interface Builder file`,可能是你需要在代码中的某个位置、在运行的时候实例化那个新的类型。我是通过调用`let t = YourClass.self`来解决的。
## row目录
### Controls Rows
Label Row
|
Button Row
|
Check Row
|
Switch Row
|
Slider Row
|
Stepper Row
|
Text Area Row
|
### Field Rows
这些rows在cell的右边有一个textField,它们的不同之处在于包含不同的大小写、自动更正和键盘类型配置。
|
TextRow
NameRow
URLRow
IntRow
PhoneRow
PasswordRow
EmailRow
DecimalRow
TwitterRow
AccountRow
ZipCodeRow
|
上面所有的`FieldRow`类型都有一个`NSFormatter`类型的`formatter`属性,可以用来控制row的value如何显示。Eureka内置了一个可以保留两位小数的formatter,`DecimalFormatter`。实例程序中包含了`CurrencyFormatter`,可以根据用户所在地显示相应的货币格式。
默认情况下,设置row的`formatter`只会影响到在没有被编辑的时候的显示格式。如果要在编辑的时候也要格式化,要在初始化row的时候把`useFormatterDuringInput`设置为`true`。编辑的时候格式化value的时候可能需要更新光标的位置,Eukera提供了下面的协议,你的formatter需要遵循这个协议来处理光标的位置:
```swift
public protocol FormatterProtocol {
func getNewPosition(forPosition forPosition: UITextPosition, inTextInput textInput: UITextInput, oldValue: String?, newValue: String?) -> UITextPosition
}
```
另外,`FieldRow`有一个`useFormatterOnDidBeginEditing`属性。当使用`DecimalRow`的时候,并且有一个允许decimal value和遵循用户所在地的formatter,例如`DecimalFormatter`,如果`useFormatterDuringInput`是`false`,`useFormatterOnDidBeginEditing`必须设置为`true`,这样value中的小数点才能匹配键盘中的小数点。
### Date Rows
Date Rows存储了一个Date,并且允许我们通过UIDatePicker来设置一个新的值。UIDatePicker的模式和date picker view的显示方法如下图:
Date Row
Picker在键盘上显示
|
Date Row (Inline)
row展开
|
Date Row (Picker)
picker一直显示
|
有三种风格(Normal、Inline和Picker),Eureka还包括:
+ **DateRow**
+ **TimeRow**
+ **DateTimeRow**
+ **CountDownRow**
### Option Rows
Option Rows关联着用户必须选择的一系列选项。
```swift
<<< ActionSheetRow() {
$0.title = "ActionSheetRow"
$0.selectorTitle = "Pick a number"
$0.options = ["One","Two","Three"]
$0.value = "Two" // initially selected
}
```
Alert Row
通过Alert的方式显示。
|
ActionSheet Row
通过action sheet的方式显示。
|
Push Row
push到一个新的controller。
|
Multiple Selector Row
像PushRow一样,但是允许多选。
|
Segmented Row
|
Segmented Row (w/Title)
|
Picker Row
通过picker view来显示通用类型选项
(也有Picker内联Row)
|
### 建立自己的自定义row?
让我们知道它,我们会跟高兴的在这提到它。:)
* **LocationRow** (在示例程序中作为自定义row)
## 安装
#### CocoaPods
[CocoaPods](https://cocoapods.org/) 是一个管理Cocoa项目的依赖。
在项目中的`Podfile`文件指定Eureka:
```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
pod 'Eureka'
```
然后运行下面的命令:
```bash
$ pod install
```
#### Carthage
[Carthage](https://github.com/Carthage/Carthage) 是一个简单的、分散的Cocoa依赖管理器。
在项目中的`Cartfile`文件文件指定Eureka:
```ogdl
github "xmartlabs/Eureka" ~> 4.0
```
#### 手动集成框架
* 在项目的根目录运行下列代码克隆Eureka,作为一个git [submodule](http://git-scm.com/docs/git-submodule)。
```bash
$ git submodule add https://github.com/xmartlabs/Eureka.git
```
* 克隆完成后,打开Eureka文件夹,然后把`Eureka.xcodeproj`拖动到Xcode项目的Project Navigator中
* 在Project Navigator选中`Eureka.xcodeproj`,检查deployment target是否跟应用的匹配
* 在Project Navigator选中你自己的项目,选择自己应用的target,然后选择在"General"选项卡,在`Embedded Binaries`点击加号
* 选择`Eureka.framework`,大功告成。
## 参与其中
* 如果你想贡献,请随时提交pull request。
* 如果你有新功能要求,请开一个issue。
* 如果你找到了一个bug,在提交新的issue之前,请先查看旧的issues。
* 如果你需要帮助,或者询问常见的问题,使用[StackOverflow]。(Tag `eureka-forms`)
**在贡献之前,请先查看[CONTRIBUTING](../CONTRIBUTING.md)文件了解更多信息。**
如果你在你的项目中使用了**Eureka**,我们很想听到这个消息。可以在[twitter]给我发消息。
## 作者
* [Martin Barreto](https://github.com/mtnBarreto) ([@mtnBarreto](https://twitter.com/mtnBarreto))
* [Mathias Claassen](https://github.com/mats-claassen) ([@mClaassen26](https://twitter.com/mClaassen26))
## 常见问题解答
#### 如果通过tag获取Row
我们可以通过调用`Form`暴露的下列方法来获取特定的row:
```swift
public func rowBy(tag: String) -> RowOf?
public func rowBy(tag: String) -> Row?
public func rowBy(tag: String) -> BaseRow?
```
例如:
```swift
let dateRow : DateRow? = form.rowBy(tag: "dateRowTag")
let labelRow: LabelRow? = form.rowBy(tag: "labelRowTag")
let dateRow2: Row? = form.rowBy(tag: "dateRowTag")
let labelRow2: BaseRow? = form.rowBy(tag: "labelRowTag")
```
#### 如果使用tag获取Section
```swift
let section: Section? = form.sectionBy(tag: "sectionTag")
```
#### 如果使用dictionary来设置form的值
调用`Form`暴露的`setValues(values: [String: Any?])`方法.
例如:
```swift
form.setValues(["IntRowTag": 8, "TextRowTag": "Hello world!", "PushRowTag": Company(name:"Xmartlabs")])
```
`"IntRowTag"`、 `"TextRowTag"`、 `"PushRowTag"` 是row tag (每个都唯一地标识一个row), `8`、 `"Hello world!"`、 `Company(name:"Xmartlabs")` 是对应的要赋给row的值。
row的value类型必须匹配dictionary中对应值的类型,否则会将nil赋给row的value。
如果form已经显示了,我们必须刷新已经显示的rows,可以通过调用`tableView.reloadData()`来刷新table view,或者调用已经显示的row的`updateCell()`方法。
#### 更改hidden或者disable condition后,Row没有更新
在设置了一个condition之后,这个condition不会自动执行,如果你想要马上执行,你可以调用`.evaluateHidden()` 或者 `.evaluateDisabled()`。
这些方法仅仅在row被添加到form时和row改变时调用。如果这个condition被更改了,但是这个row处于显示状态,必须手动执行。
#### 除非onCellHighlight也被定义了,否则onCellUnHighlight不会被调用
查看这个[issue](https://github.com/xmartlabs/Eureka/issues/96).
#### 如更新 Section header/footer
* 设置新的 header/footer ....
```swift
section.header = HeaderFooterView(title: "Header title \(variable)") // 使用 String interpolation
//或者
var header = HeaderFooterView(.class) // 使用任何view类型更灵活地设置header
header.height = { 60 } // 可以指高度
header.onSetupView = { view, section in // 每次view即将显示时,onSetupView被调用
view.backgroundColor = .orange
}
section.header = header
```
* 刷新Section
```swift
section.reload()
```
#### 如何自定义Selector 和 MultipleSelector 选项的cells
`selectableRowSetup`、`selectableRowCellUpdate` 和 `selectableRowCellSetup` 可以自定义 SelectorViewController 和 MultipleSelectorViewController的可选cells.
```swift
let row = PushRow() {
$0.title = "PushRow"
$0.options = [💁🏻, 🍐, 👦🏼, 🐗, 🐼, 🐻]
$0.value = 👦🏼
$0.selectorTitle = "Choose an Emoji!"
}.onPresent { from, to in
to.dismissOnSelection = false
to.dismissOnChange = false
to.selectableRowSetup = { row in
row.cellProvider = CellProvider>(nibName: "EmojiCell", bundle: Bundle.main)
}
to.selectableRowCellUpdate = { cell, row in
cell.textLabel?.text = "Text " + row.selectableValue! // 自定义
cell.detailTextLabel?.text = "Detail " + row.selectableValue!
}
}
```
#### 不想使用Eureka提供的自定义操作符?
正如我们所说的,`Form`和`Section`遵循`MutableCollection`和`RangeReplaceableCollection`。一个Form是一个Sections的集合,一个Sections是一个Rows的集合。
`RangeReplaceableCollection` 协议扩展提供了很多方法来改变集合。
```swift
extension RangeReplaceableCollection {
public mutating func append(_ newElement: Self.Element)
public mutating func append(contentsOf newElements: S) where S : Sequence, Self.Element == S.Element
public mutating func insert(_ newElement: Self.Element, at i: Self.Index)
public mutating func insert(contentsOf newElements: S, at i: Self.Index) where S : Collection, Self.Element == S.Element
public mutating func remove(at i: Self.Index) -> Self.Element
public mutating func removeSubrange(_ bounds: Range)
public mutating func removeFirst(_ n: Int)
public mutating func removeFirst() -> Self.Element
public mutating func removeAll(keepingCapacity keepCapacity: Bool)
public mutating func reserveCapacity(_ n: Self.IndexDistance)
}
```
上面的方法在内部用来实现自定义的操作:
```swift
public func +++(left: Form, right: Section) -> Form {
left.append(right)
return left
}
public func +=(inout lhs: Form, rhs: C) where C.Element == Section {
lhs.append(contentsOf: rhs)
}
public func <<<(left: Section, right: BaseRow) -> Section {
left.append(right)
return left
}
public func +=(inout lhs: Section, rhs: C) where C.Element == BaseRow {
lhs.append(contentsOf: rhs)
}
```
你可以在[这里](https://github.com/xmartlabs/Eureka/blob/master/Source/Core/Operators.swift)看到剩下的自定义操作符是如何实现的。
是否要用自定义的操作符,完全由你自己决定。
[要求]: #要求
[使用]: #使用
[如何创建表格]: #如何创建表格
[获取行的值]: #获取行的值
[操作符]: #操作符
[callbacks的使用]: #callbacks的使用
[Section Header和Footer]: #section-header-footer
[动态地隐藏和显示row(或者Sections)]: #hide-show-rows
[列表类型的sections]: #列表类型的sections
[有多个值的sections]: #有多个值的sections
[验证]: #验证
[滑动操作]: #滑动操作
[自定义row]: #自定义row
[简单的自定义rows]: #简单的自定义rows
[自定义内联rows]: #自定义内联rows
[自定义Presenter rows]: #presenter-rows
[row目录]: #row目录
[安装]: #安装
[常见问题解答]: #常见问题解答
[CustomCellsController]: ../Example/Example/ViewController.swift
[FormViewController]: ../Example/Source/Controllers.swift
[XLForm]: https://github.com/xmartlabs/XLForm
[DSL]: https://en.wikipedia.org/wiki/Domain-specific_language
[StackOverflow]: http://stackoverflow.com/questions/tagged/eureka-forms
[our blog post]: http://blog.xmartlabs.com/2015/09/29/Introducing-Eureka-iOS-form-library-written-in-pure-Swift/
[twitter]: https://twitter.com/xmartlabs
[EurekaCommunity]: https://github.com/EurekaCommunity
# 捐赠Eureka
所以我们可以让Eureka变得更好!
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HRMAH7WZ4QQ8E)
# 更改日志
可以在[CHANGELOG.md](../CHANGELOG.md)文件中查看。