使用
概述
内容
有关更多信息,请参阅我们在博客文章中介绍 Eureka。
(最新版本)要求
- Xcode 11+
- Swift 5.0+
示例项目
您可以通过克隆和运行示例项目来查看 Eureka 的大部分功能示例。
用法
如何创建一个表单
通过扩展 FormViewController
,您可以简单地向 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)
}
}
}
在示例中,我们创建了两个带有标准行的部分,结果是这个
您也可以通过设置 form
属性来创建一个表单,而不需要从 FormViewController
扩展,但这种方法通常更方便。
配置键盘导航插件
要更改此行为,您应设置控制器的导航选项。`FormViewController` 有一个 `navigationOptions` 变量,它是一个枚举,可以具有以下值之一
- disabled:完全无视图
- enabled:启用底部视图
- stopDisabledRow:如果导航在下一个行禁用时应停止
- skipCanNotBecomeFirstResponderRow:如果导航应跳过返回
canBecomeFirstResponder()
为 false 的行
默认值是 enabled & skipCanNotBecomeFirstResponderRow
要启用平滑滚动到屏幕外的行,通过 animateScroll
属性启用它。默认情况下,当用户在键盘导航插件中点击下一行或前一行的按钮时,包括下一行不在屏幕上时,`FormViewController`会立即在行之间跳跃。
要设置导航事件后键盘和突出显示行的间距,设置 rowKeyboardSpacing
属性。默认情况下,当表单滚动到屏幕外的视图时,在键盘顶部和行的底部之间不会留出空间。
class MyFormViewController: FormViewController {
override func viewDidLoad() {
super.viewDidLoad()
form = ...
// Enables the navigation accessory and stops navigation when a disabled row is encountered
navigationOptions = RowNavigationOptions.Enabled.union(.StopDisabledRow)
// Enables smooth scrolling on navigation to off-screen rows
animateScroll = true
// Leaves 20pt of space between the keyboard and the highlighted row after scrolling to an off screen row
rowKeyboardSpacing = 20
}
}
如果您想更改整个导航插件视图,您必须覆盖 navigationAccessoryView
变量,该变量位于您的 FormViewController
子类中。
获取行值
Row
对象持有特定类型的值。例如,SwitchRow
持有 Bool
值,而 TextRow
持有 String
值。
// Get the value of a single row
let row: TextRow? = form.rowBy(tag: "MyRowTag")
let value = row.value
// Get the value of all rows which have a Tag assigned
// The dictionary contains the 'rowTag':value pairs.
let valuesDictionary = form.values()
运算符
Eureka 包含自定义运算符,以便轻松创建表单。
+++ 添加部分
form +++ Section()
// Chain it to add multiple Sections
form +++ Section("First Section") +++ Section("Another Section")
// Or use it with rows and get a blank section for free
form +++ TextRow()
+++ TextRow() // Each row will be on a separate section
<<< 插入一行
form +++ Section()
<<< TextRow()
<<< DateRow()
// Or implicitly create the Section
form +++ TextRow()
<<< DateRow()
+= 追加数组
// Append Sections into a Form
form += [Section("A"), Section("B"), Section("C")]
// Append Rows into a Section
section += [TextRow(), DateRow()]
使用回调函数
Eureka 包含回调函数,用于更改行的外观和行为。
理解行和列
Eureka 中使用的一个抽象概念 Row
,它包含一个值得和一个视图 Cell
。该 Cell
负责管理视图并继承自 UITableViewCell
。
以下是一个示例
let row = SwitchRow("SwitchRow") { row in // initializer
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()
当行的值变化时调用。你可能想在此时调整一些参数,甚至使得其他行显示或消失。
-
onCellSelection()
每当用户点击行并且行被选中时调用。请注意,这也会对禁用行调用,因此你应在回调内部以如
guard !row.isDisabled else { return }
形式开始你的代码。 -
cellSetup()
只会在首次配置单元格时调用。在这里设置永久设置。
-
cellUpdate()
每次单元格出现在屏幕上时调用。你可以在这里使用变量(例如在 cellSetup() 中可能不存在的变量)来更改外观。
-
onCellHighlightChanged()
当单元格或任何子视图成为或不再是第一响应者时调用。
-
onRowValidationChanged()
当与行相关联的验证错误发生变化时调用。
-
onExpandInlineRow()
在展开内联行之前调用。适用于符合
InlineRowType
协议的行。 -
onCollapseInlineRow()
在折叠内联行之前调用。适用于符合
InlineRowType
协议的行。 -
onPresent()
由行在其提供另一个视图控制器之前调用。适用于符合
PresenterRowType
协议的行。使用它来设置要提供的控制器。
部分标题和页脚
你可以将字符串 String
或自定义的 View
作为部分标题或页脚设置。
字符串标题
Section("Title")
Section(header: "Title", footer: "Footer Title")
Section(footer: "Footer Title")
自定义视图
你可以使用从 .xib
文件来的自定义视图
Section() { section in
var header = HeaderFooterView<MyHeaderNibFile>(.nibFile(name: "MyHeaderNibFile", bundle: nil))
// Will be called every time the header appears on screen
header.onSetupView = { view, _ in
// Commonly used to setup texts inside the view
// Don't change the view hierarchy or size here!
}
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
}
或者只需使用回调函数构建视图
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
}()
}
动态地隐藏和显示行(或部分)
在这种情况下,我们正在隐藏和显示整个部分。
为此,每一行都有一个名为hidden
的可选类型的Condition
变量,可以使用函数或NSPredicate
设置。
使用函数条件隐藏
使用Condition
的function
情况
Condition.function([String], (Form)->Bool)
要传递的String
数组应包含依赖于此行的标签。每当这些行的任一值发生变化时,函数就会被重新评估。然后,函数接收一个Form
作为输入并返回一个Bool
值,指示该行是否应该被隐藏。这是设置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)
}
使用NSPredicate隐藏
hidden
变量还可以用NSPredicate设置。在谓词字符串中,您可以通过标签引用其他行的值来确定是否隐藏或显示行。这仅在需要检查的行的值是NSObjects(String和Int可以工作,因为它们可以桥接到它们的ObjC对应者,但枚举不行)时才有效。为什么在它们更加有限的情况下还是要使用谓词呢?因为它们可以比函数更简单、更短、更易读。看这个例子
$0.hidden = Condition.predicate(NSPredicate(format: "$switchTag == false"))
而且我们可以将它写得更短,因为Condition
遵守ExpressibleByStringLiteral
$0.hidden = "$switchTag == false"
注意:我们将替换标签为'switchTag'的行的值,而不是'$switchTag'
为了使所有这些都能正常工作,所有涉及的行都必须有一个标签,因为标签将标识它们。
我们也可以通过这样做来隐藏一行
$0.hidden = true
因为Condition
遵守ExpressibleByBooleanLiteral
。
未设置hidden
变量将使该行始终可见。
如果在表单显示后手动设置隐藏(或禁用)条件,可能需要调用row.evaluateHidden()
来强制Eureka重新评估新的条件。有关更多信息,请参见此常见问题解答部分。
章节
对于章节,这也同样是有效的。这意味着我们可以设置章节的hidden
属性来动态显示或隐藏。
禁用行
为了禁用行,每一行都有一个disabled
变量,该变量也是一个可选的Condition
类型属性。此变量与hidden
变量具有相同的操作方式,因此它需要行有一个标签。
注意,如果您想永久禁用行,也可以将disabled
变量设置为true
。
列表章节
为了显示选项列表,Eureka 包含一个特殊章节 SelectableSection
。在创建一个时,您需要传递用于选项的行的类型和 selectionType
。 selectionType
是一个枚举,可以是 multipleSelection
或 singleSelection(enableDeselection: Bool)
,其中 enableDeselection
参数确定是否可以选择行。
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
}
}
可以使用哪些类型的行?
要创建此类章节,您必须创建符合 SelectableRowType
协议的行。
public protocol SelectableRowType : RowType {
var selectableValue : Value? { get set }
}
此 selectableValue
是行值的永久存储位置。变量 value
将用于确定行是否被选中,如果被选中则为 'selectableValue',如果没有则不是。Eureka 包含 ListCheckRow
,例如。在示例项目的自定义行中还可以找到 ImageCheckRow
。
获取所选行
为了轻松获取 SelectableSection
的所选行,有两种方法:可以调用的 selectedRow()
和 selectedRows()
,以获得单个选择部分的所选行或获得多选部分的所选行。
在部分中对选项进行分组
另外,您可以使用以下 SelectorViewController
属性设置按部分分组的选项列表:
-
sectionKeyForValue
- 一个闭包,应返回特定行值的键。此键随后用于按部分分割选项。 -
sectionHeaderTitleForKey
- 一个闭包,返回为特定键的部分的标题。默认返回键本身。 -
sectionFooterTitleForKey
- 一个闭包,返回为特定键的部分的页脚标题。
多层值部分
Eureka 通过使用多层值部分支持某些字段的多个值(例如联系人中的电话号码)。它允许我们轻松创建可插入、可删除和可重新排序的部分。
如何创建多层值部分
要创建多层值部分,我们必须使用 MultivaluedSection
类型而不是常规的 Section
类型。 MultivaluedSection
扩展 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"
}
}
前面的代码片段显示了如何创建一个多层值部分。在这种情况下,我们希望根据多层值参数指示插入、删除和重新排序行。
addButtonProvider
允许我们自定义按钮行,当触摸时插入新行,而 multivaluedOptions
包含 .Insert
值。
每当需要插入新行时,Eureka 都会调用 multivaluedRowToInsertAt
隐藏属性。为了提供要添加到多值部分的行,我们应该设置此属性。Eureka 通过闭包参数传递索引。请注意,我们可以返回任何类型的行,甚至是自定义行,尽管在大多数情况下,多值部分的行具有相同的类型。
在创建一个可插入的多值部分时,Eureka 会自动添加一个按钮行。我们可以像之前解释的那样自定义此按钮行的外观。showInsertIconInAddButton
属性指示加号按钮(插入样式)是否应出现在按钮的左侧,默认为 true。
在创建插入部分时,我们需要注意一些事项。任何添加到可插入多值部分的行应位于 Eureka 自动添加的新行之上。可以通过将额外的行添加到初始化器闭包(初始化器的最后一个参数)内部来轻松实现这一点,然后 Eureka 将插入按钮添加到部分的末尾。
编辑模式
默认情况下,Eureka 仅在表单中存在多值部分时才会将 tableView 的 isEditing
设置为 true。这将在表单首次呈现时的 viewWillAppear
中完成。
有关如何使用多值部分的更多信息,请参阅 Eureka 示例项目,其中包含多个使用示例。
自定义添加按钮
如果您想使用一个不是 ButtonRow
的添加按钮,则可以使用 GenericMultivaluedSection<AddButtonType>
,其中 AddButtonType
是您想要用作添加按钮的行的类型。如果您想使用自定义行来改变按钮的 UI,这很有用。
示例
GenericMultivaluedSection<LabelRow>(multivaluedOptions: [.Reorder, .Insert, .Delete], {
$0.addButtonProvider = { section in
return LabelRow(){
$0.title = "A Label row as add button"
}
}
// ...
}
验证
Eureka 2.0.0 引入了广泛请求的内建验证功能。
行有一个 Rules
集合和一个特定配置,用于确定何时评估验证规则。
默认提供了一些规则,但您也可以创建自己的规则。
提供的规则有:
- RuleRequired
- RuleEmail
- RuleURL
- RuleGreaterThan, RuleGreaterOrEqualThan, RuleSmallerThan, RuleSmallerOrEqualThan
- 规则最小长度,规则最大长度
- 规则闭合
让我们看看如何设置验证规则。
override func viewDidLoad() {
super.viewDidLoad()
form
+++ Section(header: "Required Rule", footer: "Options: Validates on change")
<<< TextRow() {
$0.title = "Required Rule"
$0.add(rule: RuleRequired())
// This could also have been achieved using a closure that returns nil if valid, or a ValidationError otherwise.
/*
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 = .systemRed
}
}
+++ 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 = .systemRed
}
}
如前一段时间代码片段所示,我们可以通过调用行的 add(rule:)
函数,一次性设置多个规则。
行还提供了 remove(ruleWithIdentifier identifier: String)
方法来删除规则。为了使用它,我们必须在创建规则后为其分配一个ID。
有时候,我们希望在行上使用的一些规则集合,我们还想在其他行上使用。在这种情况下,我们可以使用一个 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 允许我们指定验证规则何时应该被评估。我们可以通过设置行的 validationOptions
属性来实现,它可以有以下值:
.validatesOnChange
- 当行值发生变化时进行验证。.validatesOnBlur
- (默认值)在单元格放弃第一响应者后立即进行验证。不适用于所有行。.validatesOnChangeAfterBlurred
- 在行值首次在放弃第一响应者后发生变化时进行验证。.validatesOnDemand
- 我们应手动调用validate
方法来手动验证行或表单。
如果您想验证整个表单(所有行),则可以手动调用表单的 validate
方法。
如何获取验证错误
每个行都有一个 validationErrors
属性,可以用来检索所有验证错误。这个属性只是存储了最新的行验证执行中的验证错误列表,这意味着它不会评估行的验证规则。
关于类型说明
如预期,规则必须使用与行对象相同的类型。请特别小心检查使用的行类型。您可能会看到编译器错误(“在调用中不正确的参数标记(期望 'rule:' 而得到 'ruleSet:'”)而不是指向问题的地方,当混合类型时。
滑动操作
通过使用滑动操作,我们可以在每行上定义多个 leadingSwipe
和 trailingSwipe
操作。由于滑动操作依赖于 iOS 系统功能,仅从 iOS 11.0+ 开始支持 leadingSwipe
。
让我们看看如何定义滑动操作。
let row = TextRow() {
let deleteAction = SwipeAction(
style: .destructive,
title: "Delete",
handler: { (action, row, completionHandler) in
//add your code here.
//make sure you call the completionHandler once done.
completionHandler?(true)
})
deleteAction.image = UIImage(named: "icon-trash")
$0.trailingSwipe.actions = [deleteAction]
$0.trailingSwipe.performsFirstActionWithFullSwipe = true
//please be aware: `leadingSwipe` is only available on iOS 11+ only
let infoAction = SwipeAction(
style: .normal,
title: "Info",
handler: { (action, row, completionHandler) in
//add your code here.
//make sure you call the completionHandler once done.
completionHandler?(true)
})
infoAction.actionBackgroundColor = .blue
infoAction.image = UIImage(named: "icon-info")
$0.leadingSwipe.actions = [infoAction]
$0.leadingSwipe.performsFirstActionWithFullSwipe = true
}
滑动操作需要将tableView.isEditing
设置为false
。如果有表单中的多值部分(在viewWillAppear
),Eureka将自动设置为true
。如果你在同一个表单中既有多值部分又有滑动操作,你应该根据需求设置isEditing
。
自定义行
通常,你可能需要一个与Eureka包中包括的行不同的行。如果是这种情况,你将不得不创建自己的行,但这并不困难。你可以阅读这个创建自定义行的教程以开始。你可能还想看看EurekaCommunity,其中包含一些可添加到Eureka的额外行。
基本自定义行
要创建具有自定义行为和外观的行,你可能会想创建Row
和Cell
的子类。
记住,Row
是Eureka使用的抽象,而Cell
是负责视图的真正的UITableViewCell
。由于Row
包含Cell
,因此Row
和Cell
都必须为相同的值类型定义。
// Custom Cell with value type: Bool
// The cell is defined using a .xib, so we can set 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
}
}
// The custom Row also has the cell: CustomCell and its correspond value
public final class CustomRow: Row<CustomCell>, RowType {
required public init(tag: String?) {
super.init(tag: tag)
// We set the cellProvider to load the .xib corresponding to our cell
cellProvider = CellProvider<CustomCell>(nibName: "CustomCell")
}
}
自定义行需要继承`Row`并遵循`RowType`协议。自定义单元格需要继承`Cell`并遵循`CellType`协议。
与cellSetup和CellUpdate回调类似,单元格也有设置和更新方法,您可以在其中自定义它。
自定义内联行
内联行是一种特定的行,它浮动在下面的行上方显示,通常在行被点击时,内联行会在展开和折叠模式之间切换。
因此,要创建内联行,我们需要两个行,一个是“始终”可见的行,另一个将展开/折叠。
另一个要求是这两个行的值类型必须相同。这意味着如果一个行包含一个String
类型的值,那么另一个也必须有一个String
类型的值。
一旦我们有了这两个行,我们就应该使顶层行类型符合InlineRowType
。此协议要求你定义一个`InlineRow`别名和一个`setupInlineRow`函数。`InlineRow`类型将是将展开/折叠的行类型。以下是一个例子
class PickerInlineRow<T> : Row<PickerInlineCell<T>> where T: Equatable {
public typealias InlineRow = PickerRow<T>
open var options = [T]()
required public init(tag: String?) {
super.init(tag: tag)
}
public func setupInlineRow(_ inlineRow: InlineRow) {
inlineRow.options = self.options
inlineRow.displayValueFor = self.displayValueFor
inlineRow.cell.height = { UITableViewAutomaticDimension }
}
}
《InlineRowType》将为你的内联行添加一些方法
func expandInlineRow()
func collapseInlineRow()
func toggleInlineRow()
这些方法应该可以很好地工作,但如果您想覆盖它们,请注意,必须由 `toggleInlineRow
` 调用 `expandInlineRow
` 和 `collapseInlineRow
`。
最后,当行被选中时,必须调用 `toggleInlineRow()
`,例如覆盖 `customDidSelect
`。
public override func customDidSelect() {
super.customDidSelect()
if !isDisabled {
toggleInlineRow()
}
}
自定义呈现者行
注意: 呈现者行是一个呈现新的 UIViewController 的行。
要创建一个自定义呈现者行,必须创建一个遵守 `PresenterRowType
` 协议的类。非常高推荐继承 `SelectorRow
`,因为它遵守该协议并添加了其他有用的功能。
PresenterRowType
协议定义如下
public protocol PresenterRowType: TypedRowType {
associatedtype PresentedControllerType : UIViewController, TypedRowControllerType
/// Defines how the view controller will be presented, pushed, etc.
var presentationMode: PresentationMode<PresentedControllerType>? { get set }
/// Will be called before the presentation occurs.
var onPresentCallback: ((FormViewController, PresentedControllerType) -> Void)? { get set }
}
`onPresentCallback` 会在行准备呈现另一个视图控制器时被调用。这是在 `SelectorRow
` 中完成的,所以如果您没有继承它,您将不得不自己调用它。
`presentationMode` 定义了控制器是如何呈现的,以及展示了哪个控制器。这种呈现可以使用 Segue 标识符、segue 类、以模态方式呈现控制器或将控制器推送到特定视图控制器。例如,可以像这样定义一个自定义 PushRow
让我们看一个例子。
/// Generic row type where a user must select a value among several options.
open class SelectorRow<Cell: CellType>: OptionsRow<Cell>, PresenterRowType where Cell: BaseCell {
/// Defines how the view controller will be presented, pushed, etc.
open var presentationMode: PresentationMode<SelectorViewController<SelectorRow<Cell>>>?
/// Will be called before the presentation occurs.
open var onPresentCallback: ((FormViewController, SelectorViewController<SelectorRow<Cell>>) -> Void)?
required public init(tag: String?) {
super.init(tag: tag)
}
/**
Extends `didSelect` method
*/
open override func customDidSelect() {
super.customDidSelect()
guard let presentationMode = presentationMode, !isDisabled else { return }
if let controller = presentationMode.makeController() {
controller.row = self
controller.title = selectorTitle ?? controller.title
onPresentCallback?(cell.formViewController()!, controller)
presentationMode.present(controller, row: self, presentingController: self.cell.formViewController()!)
} else {
presentationMode.present(nil, row: self, presentingController: self.cell.formViewController()!)
}
}
/**
Prepares the pushed row setting its title and completion callback.
*/
open override func prepare(for segue: UIStoryboardSegue) {
super.prepare(for: segue)
guard let rowVC = segue.destination as Any as? SelectorViewController<SelectorRow<Cell>> else { return }
rowVC.title = selectorTitle ?? rowVC.title
rowVC.onDismissCallback = presentationMode?.onDismissCallback ?? rowVC.onDismissCallback
onPresentCallback?(cell.formViewController()!, rowVC)
rowVC.row = self
}
}
// SelectorRow conforms to PresenterRowType
public final class CustomPushRow<T: Equatable>: SelectorRow<PushSelectorCell<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)
})
}
}
使用相同的行重新类定义单元格
有时候,我们想要更改某行的一个 UI 外观,但不想更改行类型和与某行关联的所有逻辑。如果使用的是从 nib 文件实例化的单元格,目前有一种方法可以做到这一点。目前,Eureka 的核心行中没有任何行是从 nib 文件实例化的,但 EurekaCommunity 中的一些自定义行是,特别是 PostalAddressRow,它已经移动到了那里。
您需要做的如下
- 创建一个包含您想创建的单元格的 nib 文件。
- 然后设置单元格类为您想要修改的现有单元格(如果您想改变更多东西,除纯 UI 之外,你应该继承该单元格)。确保该类的模块设置正确。
- 将出口连接到您的类
- 告诉您的行使用新的 nib 文件。这是通过设置 `
cellProvider
` 变量来实现使用这个 nib 的。您应该在初始化器中这样做,无论是在每个具体实例化还是在 `defaultRowInitializer
` 中。例如
<<< PostalAddressRow() {
$0.cellProvider = CellProvider<PostalAddressCell>(nibName: "CustomNib", bundle: Bundle.main)
}
您也可以为此创建一个新的行。在这种情况下,尝试从您想要修改的行的同一线公有类继承,以继承其逻辑。
在此过程中有一些需要考虑的事情
- 如果您想看一个例子,请看看PostalAddressRow 或 CreditCardRow,它们在其用例中使用了自定义 nib 文件。
- 如果收到错误信息说 "
Unknown class <YOUR_CLASS_NAME> in Interface Builder file
",可能是您必须在代码中某处实例化该新类型,以便在运行时加载它。在 my 案例中,调用 "let t = YourClass.self
" 有所帮助。
行目录
控制行
标签行 |
按钮行 |
复选行 |
开关行 |
滑块行 |
步进器行 |
文本区行 |
字段行
这些行在单元格右侧都有一个文本字段。每行之间的区别在于不同的首字母大小写、自动更正和键盘类型配置。
文本行 姓名行 网址行 整数行 电话行 密码行 电子邮件行 小数行 推特行 账号行 邮编行 |
|
所有上述 FieldRow
子类型都具有一个类型为 NSFormatter
的 formatter
属性,它可以被设置以确定该行值应该如何显示。Eureka 包含一个用于具有两位小数的数字的自定义格式化器(DecimalFormatter
)。示例项目还包含一个 CurrencyFormatter
,它可以根据用户的区域设置显示货币。
默认情况下,设置行的 formatter
只会影响值在不进行编辑时的显示。要同时格式化正在编辑的值,初始化行时将 useFormatterDuringInput
设置为 true
。在编辑时格式化值可能需要更新光标位置,而 Eureka 提供了以下协议,您应使您的格式化器遵守此协议以处理光标位置
public protocol FormatterProtocol {
func getNewPosition(forPosition forPosition: UITextPosition, inTextInput textInput: UITextInput, oldValue: String?, newValue: String?) -> UITextPosition
}
此外,FieldRow
子类型还具有一个 useFormatterOnDidBeginEditing
属性。当使用允许小数值并符合用户区域设置的格式化器(例如 DecimalFormatter
)和一个 DecimalRow
时,如果 useFormatterDuringInput
设置为 false
,则必须将 useFormatterOnDidBeginEditing
设置为 true
,以便正在编辑的值中的小数点和键盘上的小数点相匹配
日期行
日期行包含日期,并允许我们通过UIDatePicker控件设置新值。UIDatePicker的模式和日期选择器视图显示的方式是它们之间变化的。
日期行 在键盘上显示选择器。 |
行内日期行(Inline) 行展开。 |
日期选择器行(Picker) 选择器始终可见。 |
使用这3种样式(普通、内联与选择器),Eureka包括了
- 日期行
- 时间行
- 日期时间行
- 倒计时行
选项行
这些是带有与用户必须从中选择的选项列表的关联列表的行。
<<< ActionSheetRow<String>() {
$0.title = "ActionSheetRow"
$0.selectorTitle = "Pick a number"
$0.options = ["One","Two","Three"]
$0.value = "Two" // initially selected
}
警报行 可以显示一个警报,用户可以从其中选择选项。 |
动作栏行 可以显示一个操作栏,用户可以从其中选择选项。 |
推送行 可以从使用复选行列出的选项生成一个新的控制器。 |
多选选择行 类似于推送行,但允许选择多个选项。 |
分段行 |
分段行(带标题) |
选择器行 通过选择器视图显示通用类型的选项。 (也有内联选择器行) |
自己构建自定义行?
让我们知道,我们很高兴在这里提到它。:)
- 位置行(作为示例项目中自定义行包含)
安装
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。
在项目的 Podfile
中指定 Eureka
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
pod 'Eureka'
然后运行以下命令
$ pod install
Swift 包管理器
Swift 包管理器是用于管理 Swift 代码分布的工具。
设置好您的 Package.swift
清单文件后,您可以添加 Eureka 作为一个依赖,通过将其添加到 Package.swift
的依赖值中来实现。
dependencies: [ .package(url: "https://github.com/xmartlabs/Eureka.git", from: "5.4.0") ]
Carthage
Carthage 是一个简单的、去中心化的 Cocoa 依赖管理器。
在您的项目的 Cartfile
中指定 Eureka。
github "xmartlabs/Eureka" ~> 5.4
作为嵌入式框架手动添加
- 从您的项目的 git 根目录运行以下命令来克隆 Eureka 作为 git 子模块。
$ git submodule add https://github.com/xmartlabs/Eureka.git
-
打开由上一个 git 子模块命令创建的 Eureka 文件夹,并将 Eureka.xcodeproj 拖到您应用程序 Xcode 项目的 Project Navigator 中。
-
在 Project Navigator 中选择 Eureka.xcodeproj 并验证部署目标与您的应用程序部署目标相匹配。
-
在 Xcode 导航中选择您的项目,然后在侧边栏中选择您的应用程序目标。然后选择“General”选项卡,在“嵌入式二进制文件”部分下点击 + 按钮。
-
选择
Eureka.framework
,完成操作!
参与
- 如果您想 贡献,请随时 提交拉取请求。
- 如果您有 功能请求,请 开启一个问题。
- 如果您发现 bug,在提交问题之前请检查旧的问题。
- 如果您需要 帮助 或者想 提问,请使用 StackOverflow。 (标签
eureka-forms
)。
在贡献力量之前,请查看 CONTRIBUTING 文件以获取更多信息。
如果您在您的应用程序中使用了 Eureka,我们非常乐意听到您的反馈!请在twitter上给我们留言。
作者
常见问题解答 (FAQ)
如何更改单元格中显示的行值文本表示
每一行都有以下属性
/// Block variable used to get the String that should be displayed for the value of this row.
public var displayValueFor: ((T?) -> String?)? = {
return $0.map { String(describing: $0) }
}
您可以设置 displayValueFor
以根据您想要显示的字符串值来设置。
如何通过标签值获取Row
我们可以通过调用 Form
类公开的任何以下函数来获取特定的行
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")
如何通过标签值获取Section
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"
是行标签(每个标签唯一标识一行),而 8
、"Hello world!"
、Company(name:"Xmartlabs")
是分配给相应行的值。
行的值类型必须与对应字典值的类型匹配,否则将分配 nil。
如果表单已经显示,我们必须通过重新加载表格视图 tableView.reloadData()
或调用每个可见行的 updateCell()
来重新加载可见行。
更改隐藏或禁用状态后,行不会更新
设置条件后,此条件不会自动评估。如果您希望它立即执行,可以调用 .evaluateHidden()
或 .evaluateDisabled()
。
这些函数仅在行被添加到表单中以及依赖的行更改时才会调用。如果在行正在显示时更改条件,则必须手动重新评估。
只有在定义了 onCellHighlight 后,onCellUnHighlight 才会调用
查看这个问题。
如何更新 Section 的头部/尾部
- 设置新的头部/尾部数据……
section.header = HeaderFooterView(title: "Header title \(variable)") // use String interpolation
//or
var header = HeaderFooterView<UIView>(.class) // most flexible way to set up a header using any view type
header.height = { 60 } // height can be calculated
header.onSetupView = { view, section in // each time the view is about to be displayed onSetupView is invoked.
view.backgroundColor = .orange
}
section.header = header
- 重新加载 Section 以执行更改
section.reload()
如何自定义 Selector 和 MultipleSelector 选项单元格
selectableRowSetup
、selectableRowCellUpdate
和 selectableRowCellSetup
属性提供对可自定义 SelectorViewController 和 MultipleSelectorViewController 可选单元格的功能。
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! // customization
cell.detailTextLabel?.text = "Detail " + row.selectableValue!
}
}
不想使用 Eureka 独定制表符操作符?
正如我们所说明的,Form
和Section
类型都符合MutableCollection
和RangeReplaceableCollection
协议。表单是一个部分集合,部分是由行集合组成的。
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)
}
你可以在这里看到其余自定义操作符的实现。
是否选择使用 Eureka 自定制操作符取决于你。
如何从 Storyboard 设置你的表单
表单总是显示在UITableView
中。你可以在Storyboard中设置你的视图控制器,在你想放置UITableView
的位置添加它,然后将输出口连接到FormViewController的tableView
变量。这允许你为表单定义一个自定义框架(可能带有约束)。
这一切也可以通过程序化地更改FormViewController的tableView
的框架、边距等来实现。
捐赠给 Eureka
变更日志
这可以在CHANGELOG.md
文件中找到。