TheDistanceFormsPhotosVideos 0.4.1

TheDistanceFormsPhotosVideos 0.4.1

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2018年12月
SPM支持 SPM

Vlad AlexaBen BaggleyJames Shaw 维护。



  • The Distance

TheDistanceForms

Carthage Compatible MIT license

TheDistanceForms 是一个 iOS 框架,用于创建灵活的表单,作为用户输入元素的通用集合。

特性

  • 多种用户输入类型
    • 文本(单行/多行)
    • 布尔值
    • 日期和时间
    • 选择
    • 媒体(图片/视频)
  • 预建验证类型
    • 空条目
    • 电子邮件
    • 邮政编码
    • 正则表达式
  • 自定义通用验证
  • 可自定义布局和样式
  • JSON 表单创建

Default form UI Custom Layout Form Themed Form

组件库

为了保持这个库的专注,额外的功能在其他 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是一个提供让您设置表单、键盘输入和视图层次结构的默认功能的协议。

创建一个遵从FormContainerUIViewController,然后您可以按照以下方式设置您的表单:

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对象来管理用户在字段之间的导航。

Default form UI

项目库中包含一个示例应用,展示了基本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 的网站上用于验证。表单具有 titlequestions 数组,并且应该是JSON中的根对象。

{
	"title": "New Booking",
	"questions": [
		...
	]
}

每个问题都需要一个 key 和一个 question_type,并且可以可选地接受一个 validation。每种类型都有各种属性,用于自定义该问题。

自由文本输入

可以使用 TextSingleTextMultiline 添加自由文本输入。TextSingle 后备是 TextFieldStackTextMultiline 后备是 TextViewStack。它们为 UITextFieldUITextView 分别添加了显示错误和占位符功能。

TextFieldStack showing an error and placeholder.

一个简单的笔记本文本字段可以表示为

{
    "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."
    }
} 

完整详细信息可以在模式空间中找到

选项

可以选择添加简单选择,由 UISegmentedControlUIPickerView 支持背景。分段选择将它在选项数组中的索引作为其值返回。表单中唯一可指定的验证规范为 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"
    }
}

Segmented Choice view

下拉选择在 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"
    }
}

Dropdown Choices

布尔值

可以使用开关添加简单的“是/否”问题。这些可以是“选择我们的通讯”,或者您可以为确保用户已同意条款和条件添加验证。

{
    "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
}

Switch for boolean form elements

按钮

可以使用 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),该框架添加了TKTextFieldTKTextView和其他为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))

Themed Form

自定义布局

覆写默认布局并远离所有作为表格的全部数据输入。

通过创建一个新的堆栈而不是使用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 }

此外,将表单添加到表单容器而不是根视图,使表单可以作为视图层次结构的小部分。在设置键盘响应者与之前相同的情况下,导航仍然按预期工作。可以通过这种方式创建更复杂的布局。

Custom Layout Form

自定义逻辑

追踪更改以强制对日期选择的条件。

你可以进一步扩展表单子类以添加不能在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组件,可以使用简单属性显示占位符、标题和错误信息。

  • 另见:TextFieldStackTextViewStackSwitchStack

UIPicker & UIDatePicker便利性

一个NSObject子类控制器,包含用于成为UIPickerViewUIDatePickerView代理的样板代码。这简化了设置用于日期和选择选择的UITextField

通讯

  • 如果你发现了一个
    错误,请打开一个问题。
  • 如果你有一个
    功能请求,请打开一个问题。
  • 如果您想 贡献力量,请提交一个 pull request。
  • 如果您想 提出一般性问题,请通过以下邮箱联系我们:[email protected]