TheDistanceForms
TheDistanceForms 是一个 iOS 框架,用于创建灵活的表单,作为用户输入元素的通用集合。
特性
- 多种用户输入类型
- 文本(单行/多行)
- 布尔值
- 日期和时间
- 选择
- 媒体(图片/视频)
- 预建验证类型
- 空条目
- 电子邮件
- 邮政编码
- 正则表达式
- 自定义通用验证
- 可自定义布局和样式
- JSON 表单创建
组件库
为了保持这个库的专注,额外的功能在其他 The Distance 提供的库中可用。
要求
- iOS 8.0+
- Xcode 7.3+
安装
Carthage
Carthage是一个去中心化的依赖关系管理器,负责构建您的依赖关系并为您提供二进制框架。
Carthage是实现这一点的推荐方式。
使用方法
快速入门
开始使用最简单的方式是定义一个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()
返回一个 SwiftyJSON
字典,包含问题的 key
和用户的响应。如果没有响应,则在该 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表单设置
开始的最简单方法是定义一个JSON表单。完整的模式定义在仓库中(/Documentation/Schema.json),并且可以在类似 JSON Schema Lint 的网站上用于验证。表单具有 title
和 questions
数组,并且应该是JSON中的根对象。
{
"title": "New Booking",
"questions": [
...
]
}
每个问题都需要一个 key
和一个 question_type
,并且可以可选地接受一个 validation
。每种类型都有各种属性,用于自定义该问题。
自由文本输入
可以使用 TextSingle
和 TextMultiline
添加自由文本输入。TextSingle
后备是 TextFieldStack
,TextMultiline
后备是 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
UI 由 FormQuestion.init(json:)
创建。对于JSON定义中的每个问题,根据 question_type
调用 textSingleViewForQuestion(_:)
和其他 ...ViewForQuestion(_:)
方法之一。在必要时,这些方法调用 newTextSingleView()
和其他 new...View()
方法以实例化用于显示每个问题的 UIView
。
为了自定义用户界面,继承FormQuestion
类并覆盖这些方法。在创建表单时,将一个questionType
作为初始化参数指定,或者在FormContainer
创建方法中指定。
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)
使用ThemeKit进行样式设计
除了继承FormQuestion
类并在new...View()
方法中配置视图之外,TheDistanceForms还包含一个与ThemeKit
兼容的框架(TheDistanceFormsThemed),该框架添加了TKTextField
、TKTextView
和其他为UIKit组件提供的ThemeKit子类。它还添加了一个覆写new...View()
以返回ThemeKit堆栈的TKFormQuestion
类型。您可以进一步继承它来更改组件的样式(如果需要的话)。
以下是使用简单的红调配色方案和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
的一个子类,称为EventForm
,并覆盖了createFormView(_:)
方法。
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或TZStackView,具体取决于您是在iOS 9还是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
UIPicker & UIDatePicker便利性
一个NSObject
子类控制器,包含用于成为UIPickerView
或UIDatePickerView
代理的样板代码。这简化了设置用于日期和选择选择的UITextField
。
通讯
- 如果你发现了一个
错误,请打开一个问题。 - 如果你有一个
功能请求,请打开一个问题。 - 如果您想 贡献力量,请提交一个 pull request。
- 如果您想 提出一般性问题,请通过以下邮箱联系我们:[email protected]。