Made with ❤️ by XMARTLABS. This is the re-creation of XLForm in Swift.
![]() |
![]() |
![]() |
---|
有关更多信息,请查看 我们的博客文章,其中介绍了 Eureka。
您可以克隆并运行示例项目,以查看 Eureka 大多数功能的示例。
![]() |
![]() |
---|
通过扩展 FormViewController
,您可以简单地将 Section 和行添加到 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)
}
}
}
在示例中,我们创建了两个带有标准行的 Section,结果如下
您可以通过自己设置 form
属性来创建表单,而无需从 FormViewController
扩展,但是这种方法通常更方便。
要更改此行为,您应该设置控制器的导航选项。 FormViewController
有一个 navigationOptions
变量,它是一个枚举,可以具有以下一个或多个值
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
}
}
如果您想更改整个导航辅助视图,则必须在 FormViewController
的子类中覆盖 navigationAccessoryView
变量。
Row
对象保存特定类型的 value。例如,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 包括 result builders,使表单创建变得容易
// Section + Section
form = (Section("A") +++ {
URLRow("UrlRow_f1") { $0.title = "Url" }
if something {
TwitterRow("TwitterRow_f2") { $0.title = "Twitter" }
} else {
TwitterRow("TwitterRow_f1") { $0.title = "Twitter" }
}
AccountRow("AccountRow_f1") { $0.title = "Account" }
})
// Form + Section
form +++ {
if something {
PhoneRow("PhoneRow_f1") { $0.title = "Phone" }
} else {
PhoneRow("PhoneRow_f2") { $0.title = "Phone" }
}
PasswordRow("PasswordRow_f1") { $0.title = "Password" }
}
@FormBuilder
var form: Form {
Section("Section A") { section in
section.tag = "Section_A"
}
if true {
Section("Section B") { section in
section.tag = "Section_B"
}
}
NameRow("NameRow_f1") { $0.title = "Name" }
}
Eureka 包括回调以更改行的外观和行为。
Row
是 Eureka 使用的抽象,它保存一个 value 并包含视图 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
的标题或页脚。
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
}()
}
在这种情况下,我们正在隐藏和显示整个 Sections。
为了实现这一点,每行都有一个可选类型 Condition
的 hidden
变量,可以使用函数或 NSPredicate
进行设置。
使用 Condition
的 function
case
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)
}
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 重新评估新条件。有关更多信息,请参阅 此 FAQ 部分。
对于 Sections,这同样适用。这意味着我们可以设置 Section 的 hidden
属性以动态显示/隐藏它。
要禁用行,每行都有一个 disabled
变量,它也是一个可选的 Condition
类型属性。此变量的工作方式与 hidden
变量相同,因此它要求行具有标签。
请注意,如果您想永久禁用行,您也可以将 disabled
变量设置为 true
。
要显示选项列表,Eureka 包括一个名为 SelectableSection
的特殊 Section。创建时,您需要传递要在选项中使用的行类型和 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
}
}
要创建这样的 Section,您必须创建一个符合 SelectableRowType
协议的行。
public protocol SelectableRowType : RowType {
var selectableValue : Value? { get set }
}
此 selectableValue
是行的值将永久存储的位置。value
变量将用于确定行是否被选中,如果被选中,则为 'selectableValue',否则为 nil。Eureka 包括 ListCheckRow
,例如使用它。在 Examples 项目的自定义行中,您还可以找到 ImageCheckRow
。
为了轻松获取 SelectableSection
的选定行,有两种方法:selectedRow()
和 selectedRows()
,可以调用它们以获取 SingleSelection
Section 中的选定行,或者如果是 MultipleSelection
Section,则获取所有选定的行。
此外,您可以使用 SelectorViewController
的以下属性设置按 Sections 分组的选项列表
sectionKeyForValue
- 一个闭包,应返回特定行值的键。此键稍后用于按 Sections 分解选项。
sectionHeaderTitleForKey
- 一个闭包,返回特定键的 Section 的标题。默认情况下返回键本身。
sectionFooterTitleForKey
- 一个闭包,返回特定键的 Section 的页脚标题。
Eureka 通过使用多值 Sections 支持某个字段的多个值(例如联系人中的电话号码)。它允许我们轻松创建可插入、可删除和可重新排序的 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。在这种情况下,我们希望插入、删除和重新排序行,如 multivaluedOptions 参数所示。
addButtonProvider
允许我们自定义按钮行,该按钮行在点击时插入新行,而 multivaluedOptions
包含 .Insert
值。
每次需要插入新行时,Eureka 都会调用 multivaluedRowToInsertAt
闭包属性。为了提供要添加到多值 Section 的行,我们应该设置此属性。Eureka 将索引作为闭包参数传递。请注意,我们可以返回任何类型的行,甚至是自定义行,即使在大多数情况下,多值 Section 行的类型都相同。
当我们创建一个可插入的多值 Section 时,Eureka 会自动添加一个按钮行。我们可以自定义此按钮行的外观,正如我们之前解释的那样。showInsertIconInAddButton
属性指示加号按钮(插入样式)是否应出现在按钮的左侧,默认为 true。
在创建可插入的 Sections 时,我们需要考虑一些事项。添加到可插入的多值 Section 的任何行都应放置在 Eureka 自动添加以插入新行的行之上。这可以通过从 Section 的初始化程序闭包(Section 初始化程序的最后一个参数)内部将这些附加行添加到 Section 来轻松实现,这样 Eureka 就会在 Section 的末尾添加添加插入按钮。
默认情况下,仅当表单中存在 MultivaluedSection 时,Eureka 才会将 tableView 的 isEditing
设置为 true。这将在首次呈现表单的 viewWillAppear
中完成。
有关如何使用多值 Sections 的更多信息,请查看 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
的集合和特定的配置,该配置确定何时应评估验证规则。
默认情况下提供了一些规则,但您也可以创建自己的规则。
提供的规则是
让我们看看如何设置验证规则。
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:)
函数来设置任意数量的规则。
行还提供 func remove(ruleWithIdentifier identifier: String)
来删除规则。为了使用它,我们必须在创建规则后为其分配一个 id。
有时我们想要在行上使用的规则集合与我们想要在许多其他行上使用的规则集合相同。在这种情况下,我们可以使用 RuleSet
设置所有验证规则,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()
方法手动验证行或表单。如果您想验证整个表单(所有行),您可以手动调用 Form validate()
方法。
每行都有 validationErrors
属性,可用于检索所有验证错误。此属性仅保存最新行验证执行的验证错误列表,这意味着它不会评估行的验证规则。
正如预期的那样,Rules 必须使用与 Row 对象相同的类型。请格外小心检查使用的行类型。当混合类型时,您可能会看到编译器错误(“调用中的不正确的参数标签(具有 'rule:',预期为 'ruleSet:')”,但未指向问题)。
通过使用滑动操作,我们可以为每行定义多个 leadingSwipe
和 trailingSwipe
操作。由于滑动操作依赖于 iOS 系统功能,因此 leadingSwipe
仅在 iOS 11.0+ 上可用。
让我们看看如何定义滑动操作。
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
。如果表单中存在 MultivaluedSection(在 viewWillAppear
中),Eureka 将将其设置为 true
。如果您的同一个表单中同时具有 MultivaluedSections 和滑动操作,则应根据需要设置 isEditing
。
您经常需要与 Eureka 中包含的行不同的行。如果是这种情况,您将必须创建自己的行,但这应该不困难。您可以阅读 关于如何创建自定义行的本教程 以开始使用。您可能还想查看 EurekaCommunity,其中包含一些可以添加到 Eureka 的额外行。
要创建具有自定义行为和外观的行,您可能需要创建 Row
和 Cell
的子类。
请记住,Row
是 Eureka 使用的抽象,而 Cell
是负责视图的实际 UITableViewCell
。由于 Row
包含 Cell
,因此必须为相同的 value 类型定义 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")
}
}
就像回调 cellSetup 和 CellUpdate 一样,Cell
具有 setup 和 update 方法,您可以在其中自定义它。
内联行是一种特定类型的行,它动态地在其下方显示一行,通常内联行在展开和折叠模式之间切换,只要点击该行即可。
因此,要创建内联行,我们需要 2 行,即“始终”可见的行和将展开/折叠的行。
另一个要求是这两行的值类型必须相同。这意味着如果一行保存 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()
}
}
注意: Presenter 行是呈现新 UIViewController 的行。
要创建自定义 Presenter 行,您必须创建一个符合 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 类、模态呈现控制器或推送到特定的视图控制器。例如,可以像这样定义 CustomPushRow
让我们看一个例子..
/// 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,它已移至此处。
您必须做的是
cellProvider
变量以使用此 nib 来完成的。您应该在初始化程序中执行此操作,无论是在每个具体的实例化中还是使用 defaultRowInitializer
。例如<<< PostalAddressRow() {
$0.cellProvider = CellProvider<PostalAddressCell>(nibName: "CustomNib", bundle: Bundle.main)
}
您也可以为此创建一个新行。在这种情况下,尝试从与您要更改的行相同的超类继承,以继承其逻辑。
当您这样做时,需要考虑一些事项
Unknown class <YOUR_CLASS_NAME> in Interface Builder file
,则可能是您必须在代码中的某个位置实例化该新类型才能在运行时加载它。调用 let t = YourClass.self
在我的情况下有所帮助。标签行![]() |
按钮行![]() |
复选框行![]() |
开关行![]() |
滑块行![]() |
步进器行![]() |
文本区域行![]() |
这些行在单元格的右侧有一个文本字段。它们之间的区别在于不同的大小写、自动更正和键盘类型配置。
![]() |
TextRow NameRow URLRow IntRow PhoneRow PasswordRow EmailRow DecimalRow TwitterRow AccountRow ZipCodeRow |
上面所有 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
属性。当将 DecimalRow
与允许十进制值并符合用户语言环境的格式化程序(例如 DecimalFormatter
)一起使用时,如果 useFormatterDuringInput
为 false
,则必须将 useFormatterOnDidBeginEditing
设置为 true
,以便正在编辑的值中的小数点与键盘上的小数点匹配。
日期行保存日期,并允许我们通过 UIDatePicker 控件设置新值。UIDatePicker 的模式以及日期选择器视图的显示方式是它们之间的区别。
日期行 ![]() 选择器显示在键盘中。 |
日期行(内联) ![]() 行展开。 |
日期行(选择器) ![]() 选择器始终可见。 |
通过这 3 种样式(普通、内联和选择器),Eureka 包括
这些行具有与之关联的选项列表,用户必须从中选择。
<<< ActionSheetRow<String>() {
$0.title = "ActionSheetRow"
$0.selectorTitle = "Pick a number"
$0.options = ["One","Two","Three"]
$0.value = "Two" // initially selected
}
Alert Row![]() 将显示一个警报,其中包含要选择的选项。 |
ActionSheet Row![]() 将显示一个操作表,其中包含要选择的选项。 |
Push Row![]() 将推送到一个新的控制器,从中选择使用复选框行列出的选项。 |
Multiple Selector Row![]() 类似于 PushRow,但允许选择多个选项。 |
Segmented Row![]() |
Segmented Row(带标题)![]() |
Picker Row![]() 通过选择器视图呈现通用类型的选项 (还有 Picker Inline Row) |
请告诉我们,我们很乐意在此处提及它。:)
CocoaPods 是 Cocoa 项目的依赖项管理器。
将 Eureka 指定到您项目的 Podfile
中
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
pod 'Eureka'
然后运行以下命令
$ pod install
Swift Package Manager 是用于管理 Swift 代码分发的工具。
设置 Package.swift
清单文件后,可以通过将其添加到 Package.swift
的 dependencies 值中,将 Eureka 添加为依赖项。
dependencies: [ .package(url: "https://github.com/xmartlabs/Eureka.git", from: "5.5.0") ]
Carthage 是一个简单、分散的 Cocoa 依赖项管理器。
将 Eureka 指定到您项目的 Cartfile
中
github "xmartlabs/Eureka" ~> 5.5
$ git submodule add https://github.com/xmartlabs/Eureka.git
打开先前 git submodule 命令创建的 Eureka 文件夹,并将 Eureka.xcodeproj 拖到应用程序 Xcode 项目的项目导航器中。
在项目导航器中选择 Eureka.xcodeproj,并验证部署目标与您的应用程序部署目标是否匹配。
在 Xcode 导航中选择您的项目,然后从侧边栏中选择您的应用程序目标。接下来,选择“通用”选项卡,然后单击“嵌入式二进制文件”部分下的 + 按钮。
选择 Eureka.framework
,我们就完成了!
eureka-forms
)。在贡献代码之前,请查看 CONTRIBUTING 文件以获取更多信息。
如果您在您的应用中使用 Eureka,我们很乐意听到!请在 Twitter 上给我们留言。
每一行都有以下属性
/// 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
。
我们可以通过调用 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")
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()
。
这些函数仅在将行添加到表单以及它所依赖的行发生更改时调用。 如果在显示行时条件发生更改,则必须手动重新评估。
请查看此 issue。
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.reload()
提供了 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!
}
}
正如我们所说,Form
和 Section
类型符合 MutableCollection
和 RangeReplaceableCollection
协议。 Form 是 Section 的集合,Section 是 Row 的集合。
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 自定义运算符取决于您自己。
表单始终显示在 UITableView
中。 您可以在 storyboard 中设置您的视图控制器,并在您希望的位置添加 UITableView,然后将 outlet 连接到 FormViewController 的 tableView
变量。 这允许您为表单定义自定义框架(可能带有约束)。
所有这些也可以通过以编程方式更改 FormViewController 的 tableView
的框架、边距等来完成。
这可以在 CHANGELOG.md 文件中找到。