由XMARTLABS精心编写,是XLForm的Swift版本。
想了解更多,可以查看我们的关于Eureka的博客。
- Xcode 9.2+
- Swift 4+
你可以clone这个项目,然后运行Example来欣赏Eureka的大部分特性。
通过继承 FormViewController
,你可以很容易地把sections和rows添加到form
变量。
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,键盘的顶部和编辑行的底部是没有间距的。
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
。
// 获取单个row的值
let row: TextRow? = form.rowBy(tag: "MyRowTag")
let value = row.value
// 获取表格中所有rows的值(必须给每个row的tag赋值)
// 字典中包含的键值对为:['rowTag': value]。
let valuesDictionary = form.values()
Eureka包含了自定义的操作符,使得我们更容易地创建表格:
form +++ Section()
// 连起来添加多个sections
form +++ Section("First Section") +++ Section("Another Section")
// 或者直接利用行来创建一个空白的section
form +++ TextRow()
+++ TextRow() // 每个row显示在一个独立的section
form +++ Section()
<<< TextRow()
<<< DateRow()
// 或者隐式地创建一个section
form +++ TextRow()
<<< DateRow()
// 添加多个Sections到表格中
form += [Section("A"), Section("B"), Section("C")]
// 添加多个rows到一个section中
section += [TextRow(), DateRow()]
Eureka包含了很多callbacks来更改行的外观和行为。
Row
是抽象的,是Eureka用来存储 value 的并包含了一个Cell
。这个Cell
用来管理view,并继承自UITableViewCell
。
例子:
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)
}
-
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。
你可以将String
的title或者自定义的View
作为Section
的header或者footer。
Section("Title")
Section(header: "Title", footer: "Footer Title")
Section(footer: "Footer Title")
你可以使用一个.xib
作为自定义View:
Section() { section in
var header = HeaderFooterView<MyHeaderNibFile>(.nibFile(name: "MyHeaderNibFile", bundle: nil))
// header每次出现在屏幕的时候调用
header.onSetupView = { view, _ in
// 通常是在这修改view里面的文字
// 不要在这修改view的大小或者层级关系
}
section.header = header
}
或者是一个使用纯代码创建的UIView
Section(){ section in
var header = HeaderFooterView<MyCustomUIView>(.class)
header.height = {100}
header.onSetupView = { view, _ in
view.backgroundColor = .red
}
section.header = header
}
或者直接用callback来创建view
Section(){ section in
section.header = {
var header = HeaderFooterView<UIView>(.callback({
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .red
return view
}))
header.height = { 100 }
return header
}()
}
在这个例子里,我们隐藏或显示整个sections。
为了达到这个效果,每个row有一个Condition
的可选类型的变量hidden
,Condition
可以通过function
或者NSPredicate
来设置。
使用Condition
的function
case:
Condition.function([String], (Form)->Bool)
function
需要一个Form
参数,并返回Bool
,决定当前row是否需要隐藏。这是一个非常强大的设置hidden
的方式,因为它没有明显的限制。
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!"
}
public enum Condition {
case function([String], (Form)->Bool)
case predicate(NSPredicate)
}
hidden
也可以用NSPredicate来设置。在predicate string里面,你可以引用其他row的tags,然后决定一个row是否需要隐藏。
但是使用NSPredicate来设置hidden
的方式只适用于其他rows的value继承自NSObject(String 和 Int 也适用,因为它们被桥接到OjbC对应的类型,但是enums不适用)
使用NSPredicate限制这么多,我们为什么要使用它呢? 因为它比function更简单、更短而且更易读。例如下面这个例子:
$0.hidden = Condition.predicate(NSPredicate(format: "$switchTag == false"))
我们还可以写的更简单,因为Condition
遵循ExpressibleByStringLiteral
:
$0.hidden = "$switchTag == false"
注意:我们会在执行的时候把'$switchTag'替换成tag为switchTag的row的value。
所以的rows都必须有一个tag,我们会用这个tag去找到对应的row,这样才能达到我们想要的效果。
我们也可以直接隐藏一个row:
$0.hidden = true
因为Condition
遵循ExpressibleByBooleanLiteral
.
如果不设置hidden
变量,那么对应的rows就会一直显示。
对于section来说,也是一样的。我们也可以通过设置hidden
属性来控制显示或隐藏。
为了禁用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
.
为了显示一个列表选项,Eukera有一个特殊的section,叫做SelectableSection
。
当创建SelectableSection
的时候,你需要传入在选项中使用的row的类型和selectionTyle
。selectionTyle
是一个枚举,multipleSelection
或者 singleSelection(enableDeselection: Bool)
(enableDeselection
决定选中的rows是否可以取消选中)。
form +++ SelectableSection<ListCheckRow<String>>("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<String>(option){ listRow in
listRow.title = option
listRow.selectableValue = option
listRow.value = nil
}
}
为了创建这样的section,你必须先创建一个遵循SelectableRowType
协议的row。
public protocol SelectableRowType : RowType {
var selectableValue : Value? { get set }
}
selectableValue
是row的value,将会被永久存储。而value
变量将会用来决定row是否选中,如果被选中,它的值就等于selectableValue
,否则为nil。Eureka包含上面例子用到的ListCheckRow
,在自定义rows的实例程序中,你还可以找到ImageCheckRow
。
为了获得SelectableSection
中被选中的row,Eukera提供了两个方法:selectedRow()
(获取SingleSelection
选中的row) and selectedRows()
获取MultipleSelection
所有选中的rows。
另外,你可以使用SelectorViewController
的属性把选项列表分组。
-
sectionKeyForValue
- 是一个闭包,返回特定row的value对应的key,这个key被用来把选项分组。 -
sectionHeaderTitleForKey
- 是一个闭包,返回特定key对应的section的header title,默认是key本身的值。 -
sectionFooterTitleForKey
- 是一个闭包,返回特定key对应的section的footer title。
Eureka可以通过有多个值的section来支持一个字段对应多个值的情况。它允许我们很容易地创建能插入的、能删除的和能排序的sections。
为了创建一个多值section,我们要使用MultivaluedSection
,而不是常规的Section
。MultivaluedSection
继承自Section
,拥有一些额外的属性来设置多值section的行为。
让我们来看一个例子:
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
让我们看看如何设置验证规则。
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<String> { 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
来设置所以规则:
var rules = RuleSet<String>()
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 以上的系统。
让我们看看如何定义滑动操作:
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
。
通常你需要自定义不同于Eureka内置的row。这其实不是很难,你可以阅读如何自定义rows的教程来开始。你也可以看看EurekaCommunity,这里包含了其他rows,并准备加入到Eukera中。
为了创建一个拥有自定义行为和外观的row,你可能需要继承于Row
和Cell
。
请记住Row
是Eureka使用的抽象类;而Cell
实际上是UITableViewCell
,用于管理view。
因为Row
包含了Cell
,所以Row
和Cell
必须同时定义。
// 自定义value类型是Bool的Cell
// Cell是使用 .xib 定义的,所以我们可以直接设置outlets
public class CustomCell: Cell<Bool>, 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<CustomCell>, RowType {
required public init(tag: String?) {
super.init(tag: tag)
// 我们把对应CustomCell的 .xib 加载到cellProvidor
cellProvider = CellProvider<CustomCell>(nibName: "CustomCell")
}
}
自定义rows需要继承自 Row<CellType>
,并遵循RowType
协议。
自定义的cells必须继承自Cell<ValueType>
,并遵循CellType
协议。
就像cellSetup和CellUpdate回调一样,Cell
有setup和update方法,你可以在里面做自定义。
内联row是一个特定的row类型,可以在它下面动态的显示一个row。正常来说内联row在被点击时在展开和折叠两种状态切换。
所以,为了创建一个内联row,我们需要两个rows,一个总是显示的row,另外一个被展开和折叠的row。
另外一个要求是,这两个rows的value类型必须是一样的。
一旦我们拥有了两个rows,我们要让第一个row遵循InlineRowType
,这将会给第一个row添加一些方法:
func expandInlineRow()
func hideInlineRow()
func toggleInlineRow()
最后,当row被点击时我们要调用toggleInlineRow()
,例如重写customDidSelect()
方法:
public override func customDidSelect() {
toggleInlineRow()
}
注意: 一个Presenter row 是可以弹出UIViewController的row。
为了创建一个Presenter rows,必须创建一个遵循PresenterRowType
的类。高度推荐继承自SelectorRow
,因为它遵循了那个协议并且添加了其他很有用的方法。
PresenterRowType
协议的定义如下:
public protocol PresenterRowType: TypedRowType {
typealias ProviderType : UIViewController, TypedRowControllerType
var presentationMode: PresentationMode<ProviderType>? { get set }
var onPresentCallback: ((FormViewController, ProviderType)->())? { get set }
}
onPresentCallback
将会在row即将显示另外一个view controller的时候调用。在SelectorRow
里面已经调用了,如果你没有继承自它的话,你需要自己手动调用。
presentationMode
定义了应该显示哪个controller和怎么显示controller。我们可以通过Segue identifier、segue class、present或者push来展示controller。例如一个CustomPushRow可以像这样定义:
public final class CustomPushRow<T: Equatable>: SelectorRow<PushSelectorCell<T>, SelectorViewController<T>>, RowType {
public required init(tag: String?) {
super.init(tag: tag)
presentationMode = .show(controllerProvider: ControllerProvider.callback {
return SelectorViewController<T>(){ _ in }
}, onDismiss: { vc in
_ = vc.navigationController?.popViewController(animated: true)
})
}
}
你可以用自己的UIViewController替换SelectorViewController<T>
,用自己的cell替换PushSelectorCell<T>
。
有时候我们想要改变我们其中一个row的外观,但是不需要改变row的类型和已有的逻辑。如果我们使用的cell是通过xib
文件来初始化的,那么现在有一种方法能达到我们的要求。目前,所有Eureka内置的rows都不是通过xib
来初始化的,但是EurekaCommunity的一些自定义rows是通过xib
初始化,例如PostalAddressRow。
你所需要做的是:
- 创建一个包含你想要创建的cell的nib文件。
- 然后把cell的class设置为你想要修改的cell(如果你想要更改除了UI以外的东西,你需要子类化那个cell。),并且确保那个cell的module是正确的。
- 把outlets连接到类中
- 告诉你的row使用这个新的nib文件。这是通过设置
cellProvider
来完成的,这个设置需要在具体的初始化过程或者defaultRowInitializer
里面完成。例如:
<<< PostalAddressRow() {
$0.cellProvider = CellProvider<PostalAddressCell>(nibName: "CustomNib", bundle: Bundle.main)
}
另外,你也可以创建一个新的row来达到目的。这种情况下你要继承同一个父类,让这个row继承它的逻辑。
当我们在实现这个的时候,有些东西可以考虑下:
- 如果你想要看例子,你可以看看PostalAddressRow或者CreditCardRow,它们都是用了自定义的nib文件。
- 如果你遇到了这样的错误:
Unknown class <YOUR_CLASS_NAME> in Interface Builder file
,可能是你需要在代码中的某个位置、在运行的时候实例化那个新的类型。我是通过调用let t = YourClass.self
来解决的。
Label Row |
Button Row |
Check Row |
Switch Row |
Slider Row |
Stepper Row |
Text Area Row |
这些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需要遵循这个协议来处理光标的位置:
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,并且允许我们通过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关联着用户必须选择的一系列选项。
<<< ActionSheetRow<String>() {
$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) |
让我们知道它,我们会跟高兴的在这提到它。:)
- LocationRow (在示例程序中作为自定义row)
CocoaPods 是一个管理Cocoa项目的依赖。
在项目中的Podfile
文件指定Eureka:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
pod 'Eureka'
然后运行下面的命令:
$ pod install
Carthage 是一个简单的、分散的Cocoa依赖管理器。
在项目中的Cartfile
文件文件指定Eureka:
github "xmartlabs/Eureka" ~> 4.0
- 在项目的根目录运行下列代码克隆Eureka,作为一个git submodule。
$ 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文件了解更多信息。
如果你在你的项目中使用了Eureka,我们很想听到这个消息。可以在twitter给我发消息。
我们可以通过调用Form
暴露的下列方法来获取特定的row:
public func rowBy<T: Equatable>(tag: String) -> RowOf<T>?
public func rowBy<Row: RowType>(tag: String) -> Row?
public func rowBy(tag: String) -> BaseRow?
例如:
let dateRow : DateRow? = form.rowBy(tag: "dateRowTag")
let labelRow: LabelRow? = form.rowBy(tag: "labelRowTag")
let dateRow2: Row<DateCell>? = form.rowBy(tag: "dateRowTag")
let labelRow2: BaseRow? = form.rowBy(tag: "labelRowTag")
let section: Section? = form.sectionBy(tag: "sectionTag")
调用Form
暴露的setValues(values: [String: Any?])
方法.
例如:
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处于显示状态,必须手动执行。
查看这个issue.
- 设置新的 header/footer ....
section.header = HeaderFooterView(title: "Header title \(variable)") // 使用 String interpolation
//或者
var header = HeaderFooterView<UIView>(.class) // 使用任何view类型更灵活地设置header
header.height = { 60 } // 可以指高度
header.onSetupView = { view, section in // 每次view即将显示时,onSetupView被调用
view.backgroundColor = .orange
}
section.header = header
- 刷新Section
section.reload()
selectableRowSetup
、selectableRowCellUpdate
和 selectableRowCellSetup
可以自定义 SelectorViewController 和 MultipleSelectorViewController的可选cells.
let row = PushRow<Emoji>() {
$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<ListCheckCell<Emoji>>(nibName: "EmojiCell", bundle: Bundle.main)
}
to.selectableRowCellUpdate = { cell, row in
cell.textLabel?.text = "Text " + row.selectableValue! // 自定义
cell.detailTextLabel?.text = "Detail " + row.selectableValue!
}
}
正如我们所说的,Form
和Section
遵循MutableCollection
和RangeReplaceableCollection
。一个Form是一个Sections的集合,一个Sections是一个Rows的集合。
RangeReplaceableCollection
协议扩展提供了很多方法来改变集合。
extension RangeReplaceableCollection {
public mutating func append(_ newElement: Self.Element)
public mutating func append<S>(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<S>(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<Self.Index>)
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)
}
上面的方法在内部用来实现自定义的操作:
public func +++(left: Form, right: Section) -> Form {
left.append(right)
return left
}
public func +=<C : Collection>(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 +=<C : Collection>(inout lhs: Section, rhs: C) where C.Element == BaseRow {
lhs.append(contentsOf: rhs)
}
你可以在这里看到剩下的自定义操作符是如何实现的。
是否要用自定义的操作符,完全由你自己决定。
可以在CHANGELOG.md文件中查看。