测试已测试 | ✗ |
Lang语言 | SwiftSwift |
许可 | MIT |
Released最后发布 | 2017年6月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Vlad Alexa,Ben Baggley,James Shaw 维护。
TheDistanceForms是一个iOS框架,用于创建灵活的表单,作为用户输入元素的通用集合。
为了保持此库的集中性,其他库中提供了额外的功能,这些库由The Distance提供。
开始的最简单方法是定义一个 JSON 表单。一旦定义了您的表单,您将使用您的数据的 SwiftyJSON 表示创建一个 Form
对象。
guard let data = NSData(contentsOfURL: jsonURL),
let form = Form(definition: JSON(data:data))
else {
return nil
}
这持有表单中所有的视图和问题的引用。《FormContainer》是一个协议,它为设置表单、键盘输入和视图层次结构提供默认功能。
创建一个遵循 FormContainer
的 UIViewController
并可以按以下方式设置表单
var form:Form?
var keyboardResponder:KeyboardResponder?
override func viewDidLoad() {
super.viewDidLoad()
guard let jsonURL = NSBundle.mainBundle().URLForResource("Form", withExtension: "json"),
let form = addFormFromURL(jsonURL, toContainerView: scrollView, withInsets: UIEdgeInsetsMake(16.0, 16.0, 16.0, 8.0))
else { return }
self.form = form
keyboardResponder = setUpKeyboardResponder(onForm: form, withScrollView: scroll)
}
addFormFromURL(_:toContainerView:withInsets)
创建表单,并将所有的提问按垂直堆叠添加到 containerView
参数中,在这种情况下是一个 UIScrollView
实例。这是默认UI
setUpKeyboardResponder(onForm:withScrollView:)
配置一个 KeyboardResponder 对象,以管理用户在字段之间的导航。
仓库项目包含一个示例应用程序,其中包含基本JSON表单和自定义表单的示例。
表单验证按问题逐个进行,当该问题字段第一次不再响应或其他情况时执行验证,比如调用form.validateForm()
或form.validateValues()
中的前者先行。JSON中指定的验证有限,但如果在代码中指定,则可以使用验证类型对象执行任何验证。
public struct Validation<Type> { ... }
以下提供了便捷初始化器:
验证元素也可以组合,例如电话或电子邮件地址。
从表单获取答案的最简单方法是通过添加一个提交按钮。默认的FormContainer
实现为每个Button
添加了一个目标,它将调用到buttonTappedForQuestion(_:)
。您可以通过使用question.key
来确定您的表单中哪个按钮被点击,进而响应用户的提交按钮。
可以使用form.validateForm()
来获取每个问题的ValidationResult
数组。您可以根据这个结果来显示警告,或者如果总结果是.Valid
,则将用户的响应提交到服务器。
form.answersJSON()
返回一个关于问题的key
和用户响应的SwiftyJSON
字典。如果没有响应,则为该key
添加null
。
{
"birthday" : null,
"first_name" : "Josh",
"station" : "London King's Cross",
"newsletter" : true,
"contact" : 1,
"event_start" : "2016-04-25 15:35:03 +0000"
}
最简单的开始方式是定义一个JSON表单。完整的模式定义在存储库中(/Documentation/Schema.json),可用于在类似JSON Schema Lint的网站上进行验证。表单有一个title
和一个questions
数组,并且应该是JSON的根对象。
{
"title": "New Booking",
"questions": [
...
]
}
每个问题都需要一个key
和一个question_type
,并且可以选择添加一个validation
。每种类型都有各种自定义选项的属性。
可以使用TextSingle
和TextMultiline
添加自由文本输入。这两个类型分别采用一个TextFieldStack
和TextViewStack
。它们分别为UITextField
和UITextView
添加显示错误和占位符功能。
以下示例是一个简单的注释文本字段:
{
"key": "notes",
"question_type": "TextMultiline",
"prompt": "Notes"
}
这将添加一个带有占位符文本“注释”的文本视图到您的表单中。
还有更多的配置可用,比如一个带有验证和额外大写的标题文本字段可能如下所示:
{
"key": "booking_title",
"question_type": "TextSingle",
"prompt": "Title",
"capitalization": "Words",
"validation": {
"type": "NotEmpty",
"value_type": "String",
"message": "Please enter a title for your booking."
}
}
完整细节可在模式空间中找到
简单的选择可以通过采用UISegmentedControl
或UIPickerView
的方式添加。分段选择将选项返回为数组中的索引。表单中只能指定的验证是NotEmpty
。因为没有文本视图用于提示,所以分段选项有一个标题和一个副标题。
{
"key": "contact",
"question_type": "ChoiceSegments",
"title": "Contact Preferences",
"subtitle": "How would you like us to keep in touch with you?",
"choices":[
"Email",
"Phone",
"SMS"
],
"validation": {
"type": "NotEmpty",
"value_type": "Number",
"message": "You must select a preference"
}
}
下拉选择通过UIPickerView
显示其选项,可以用于较长的选项列表。验证使用字符串,因此可用的验证类型更多。
{
"key": "station",
"question_type": "ChoiceDropdown",
"prompt": "Select a station",
"choices":[
"London King's Cross",
"Peterborough",
"York",
"Darlington",
"Durham",
"Newcastle",
"Edinburgh Weverley"
],
"validation": {
"type": "NotEmpty",
"value_type": "String",
"message": "You must select a station"
}
}
可以使用开关添加简单的是/否问题。这些可能是“是否订阅我们的通讯录”或者您可以通过添加验证来确保用户已经同意条款和条件。
{
"key": "newsletter",
"question_type": "Boolean",
"title": "Newsletter",
"subtitle": "Would you like to keep up to date with all our latest news and offers?",
"default": false
}
可以使用Button
问题类型将按钮添加到表单中。
{
"key": "view_terms",
"question_type": "Button",
"title": "View Terms & Conditions"
}
虽然JSON很方便,但不能表达框架的所有功能。您可以使用Form
的子类创建一个表单,并添加额外功能。这可以启用自定义UI、逻辑、布局和验证。
UI是通过FormQuestion.init(json:)
创建的。对于JSON定义中的每个问题,根据question_type
,会调用textSingleViewForQuestion(_:)
和其它...ViewForQuestion(_:)
方法中的一种。当需要时,这些方法会调用newTextSingleView()
和其它new...View()
方法来实例化用于显示每个问题的UIView
。
要自定义UI,可以继承FormQuestion
并重写这些方法。创建表单时,可以通过初始化参数或通过在FormContainer
创建者方法中指定来指定questionType
。
class MyFormQuestion {
override func newTextSingleView() -> TextFieldStack {
let stack = super.newTextSingleView
stack.textField.font = ...
stack.errorLabel.textColor = ...
return stack
}
}
当创建您的表单时
self.form = Form(definition: JSON(data:data), questionType: MyFormQuestion.self)
除了继承FormQuestion
并在new...View()
方法中配置视图外,TheDistanceForms还包含一个与ThemeKit
兼容的框架(TheDistanceFormsThemed),它为UIKit组件添加了TKTextField
、TKTextView
等ThemeKit子类。它还添加了一个新的TKFormQuestion
类型,该类型重写了new...View()
以返回ThemeKit堆栈。如有需要,您可以进一步继承它以更改组件的样式。
以下是一个使用简单的主题(红色强调色和Avenir Next字体样式)创建的测试表单示例。
let form = addFormFromURL(jsonURL, questionType: TKFormQuestion.self, toContainerView: scroll, withInsets: UIEdgeInsetsMake(16.0, 16.0, 16.0, 8.0))
覆盖默认布局,并远离所有表格数据输入。
可以通过创建一个新的堆叠而不是使用formView
变量来实现自定义布局。可以使用从KeyedView
中获得的elementForKey(_:)
方法访问视图。密钥是问题密钥,视图是QuestionView
的UIView。通过这种方式,自定义视图可以按不同的方式堆叠视图,以便实现字段如开始和结束时间或标题、名字和姓氏的并列布局。
示例应用程序包含一个EventForm.json
定义。它包含一个TextSingle
、两个DateTime
和一个TextMultiline
字段。通过覆盖Form
的子类中的createFormView(_:)
方法,我们可以将日期字段并排放置,该子类被称为EventForm
。
class EventForm: Form {
override func createFormView() -> StackView {
// 1. Test Views exist
guard let titleView = self.viewKeys["event_title"],
let startView = self.viewKeys["start_date"],
let endView = self.viewKeys["end_date"],
let notesView = self.viewKeys["notes"]
else { return super.createFormView() }
// 2. Align the dates horizontally
var dateStack = CreateStackView([startView, endView])
dateStack.spacing = 8.0
dateStack.stackDistribution = .FillEqually
// 3. Stack all components vertically
var stack = CreateStackView([titleView, dateStack.view, notesView])
stack.axis = .Vertical
stack.spacing = 16.0
return stack
}
}
我们使用StackView
来创建一个UIStackView(如果您正在运行iOS 9)或TZStackView
(如果您正在运行iOS 8)。
然后可以指定这种版本的表单,当调用FormContainer
方法将表单添加到我们的视图层次结构时。
guard let jsonURL = NSBundle.mainBundle().URLForResource("EventForm", withExtension: "json"),
let form = addFormFromURL(jsonURL, ofType: EventForm.self, toContainerView: formContainer, withInsets: UIEdgeInsetsZero)
else { return }
表单还添加到表单容器中,而不是根视图,这样表单就可以作为视图层次结构中的一部分。提供键盘响应者被设置为之前的状态,导航功能如预期的一样正常工作。可以利用这种方法创建更复杂的布局。
跟踪日期选择更改以强制条件。
您可以通过扩展表单子类来添加无法用JSON格式指定的逻辑。以日期为例,我们可以限制日期必须在将来,并确保结束日期晚于开始日期。
private(set) var startObserver:ObjectObserver!
required init?(definition: JSON, questionType: FormQuestion.Type) {
super.init(definition: definition, questionType: questionType)
// 1. Get the date controllers
guard let startView = questionForKey("start_date")?.questionView,
case let .DateTime(startStack, startController) = startView,
let endView = questionForKey("end_date")?.questionView,
case let .DateTime(_, endController) = endView
else {
return nil
}
// 2. Set the minimum dates to now
startController.datePicker.minimumDate = NSDate()
endController.datePicker.minimumDate = NSDate()
// 3. When the start date changes, update the end date to be later with a minimum duration
startObserver = ObjectObserver(keypath: "text", object: startStack.textField, completion: { (keypath, object, change) in
endController.datePicker.minimumDate = startController.datePicker.date.dateByAddingTimeInterval(30 * 60)
})
}
这样我们可以在它们之间建立复杂的关联。
基于其他用户输入实现验证
注册示例是一个典型的注册表单,包括姓名、电子邮件地址和密码。注意电子邮件和密码字段的额外定制。密码字段还通过正则表达式强制执行密码的给定格式。
{
"key": "password",
"question_type": "TextSingle",
"prompt": "Password",
"capitalization": ".None",
"auto_correction": false,
"secure_text_entry": true,
"validation": {
"type": "Regex",
"value_type": "String",
"regex": "^[a-zA-Z0-9]{6,}$",
"message": "Please enter a password at least 6 characters long containing only letters and numbers."
}
}
我们无法在JSON中指定确认字段匹配。我们可以通过覆盖表单初始化器并手动创建一个Validation<String>
对象来实现这一点。
required init?(definition: JSON, questionType: FormQuestion.Type) {
super.init(definition: definition, questionType: questionType)
guard let passwordView = questionForKey("password")?.questionView,
case let .TextSingle(passwordStack) = passwordView,
let confirmView = questionForKey("confirm_password")?.questionView,
case let .TextSingle(confirmStack) = confirmView
else {
return nil
}
confirmStack.validation = Validation(message: "Your passwords must match.", validation: { (value) -> Bool in
return value == passwordStack.text
})
}
为了促进表单创建,已添加多个类以补充标准UIKit元素。
一组UI组件,可以使用简单的属性显示占位符、标题和错误消息。
TextFieldStack
、TextViewStack
、SwitchStack
一个包含用于成为UIPickerView
或UIDatePickerView
代理的样板代码的NSObject
子类。这简化了设置用作日期和选择选择的UITextField
。