Declarative form building framework for iOS

Related tags

Form Formalist
Overview

Formalist Carthage compatible CocoaPods Compatible

Swift framework for building forms on iOS

Formalist is a Swift framework for building forms on iOS using a simple, declarative, and readable syntax.

Table of Contents

Example

Formalist Example App

The example app (shown above) demonstrates how to build a simple form using the included form elements.

This code snippet from the example app is used to render the first section of the form:

group(configuration: groupedConfiguration, elements: [
    toggle(title: "Boolean Element", value: self.booleanValue),
    textView(value: self.textViewValue) {
        $0.placeholder = "Text View Element"
    },
    singleLineFloatLabel(name: "Float Label Element", value: self.floatLabelValue),
    segments(title: "Segment Element", segments: [
        Segment(content: .Title("Segment 1"), value: "Segment 1"),
        Segment(content: .Title("Segment 2"), value: "Segment 2")
    ], selectedValue: self.segmentValue),
])

Note that the above code example uses the built-in convenience functions for constructing each form element, which are syntactic sugar for improving readability over the standard class initializers.

Installation

In addition to manually integrating the framework into your Xcode project, the following package managers are supported:

CocoaPods

Add the following line to your Podfile:

pod 'Formalist', '~> 0.1.0', :submodules => true

Carthage

Add the following line to your Cartfile:

github "seedco/Formalist" ~> 0.1.0

Documentation

FormValue

FormValue is a reference type that wraps form values and allows for observation of those values. After an instance is instantiated with an initial value, the consumer code cannot mutate the value directly. FormValue instances are passed into the initializer of form elements (described in the next section), and those form elements bind the UI controls to the FormValue instance such that any changes made by the user using those controls result in a modification of the underlying value.

The framework consumer can maintain a reference to a FormValue instance to access its value via the value property at any time. The consumer can also implement KVO-style observation of the value by attaching block-based observers using the FormValue.addObserver(_:) function.

FormElement

The FormElement protocol, as its name suggests, is the protocol that all form elements must implement. It contains a single method with the signature func render() -> UIView, which is called to render the view for that form element.

A typical implementation of FormElement does the following:

  • Initializer
    • Accepts one or more FormValue parameters, which are bound to the UI controls
    • Optionally accepts additional configuration parameters. These parameters are typically related to behaviour rather than appearance, since appearance related properties can be set directly on the view using an optional view configuration block. An example of a behaviour related parameter is the continuous parameter to TextFieldElement, which determines whether the form value is updated continuously as text is edited.
    • Optionally accepts a view configuration block that can be used to perform additional customization on the element view. This should be the last parameter to the initializer so that trailing closure syntax can be used.
  • render() function
    1. Creates the element view
    2. Configures any default properties on the element view
    3. Sets up target-action or another callback mechanism to be notified when a value changes, in order to update the corresponding FormValue instance
    4. Invoke the optional view configuration block as the last step for additional view customization
    5. Return the view

Responder Chain

The framework implements support for chaining form element views using a responder chain-like construct. This is currently used to implement the built-in tabbing behaviour to switch between text fields in a form by pressing the "return" key on the keyboard.

This behaviour can be supported in custom form elements using the following two steps:

  1. Ensure that the view being returned from the render() function returns true for canBecomeFirstResponder()
  2. When the view needs to shift focus to the next form view that supports first responder status, it should access the next responder via the nextFormResponder property (added in a UIView extension by the framework) and call becomeFirstResponder() on it. For example, in TextFieldElement, this happens as a result of a delegate method being called that indicates that the return key has been pressed.

ValidationRule

ValidationRule wraps validation logic for a value of a particular type. Notably, validation rules execute asynchronously -- this means, for example, that you can have a validation rule that kicks off a network request to validate a form value.

A ValidationRule is initialized using a block that takes a value and a completion handler as parameters. The block contains the logic necessary to perform the validation and call the completion handler with the result of the validation (one of Valid, Invalid(message: String), or Cancelled). A failed validation will cause the validation failure message to be presented to the user underneath the form element that caused the failure.

Static computed variables on ValidationRule define several built in rules for common cases like required fields and email addresses, and ValidationRule.fromRegex() provides a simple way to create a rule that validates a string input using a regular expression.

Validation rules are passed into the constructor of a form element that supports them, like TextFieldElement.

Validatable

Validatable is the protocol that form elements must implement to support validation of their values. It contains a single method with the signature func validate(completionHandler: ValidationResult -> Void), which should be implemented to perform the validation and calls the completion handler with the validation result. The most common implementation of this method is simply a call to ValidationRule.validateRules, which validates a value using a given array of rules.

GroupElement

Group Element

GroupElement is the main non-leaf node type in a form element hierarchy, and implements a number of important behaviours related to the rendering, layout, validation, and interaction amongst a group of form elements.

It implements two styles: Plain, which renders the group with no background color and no separators, and Grouped, which renders the group with a given background color and separators by default.

The GroupElement.Configuration object passed into the initializer can be used to tweak most aspects of the group appearance and behaviour, including the style, layout, separator views, and validation error views.

Examples

Using constant height form elements
var configuration = GroupElement.Configuration()
configuration.layout.mode = .ConstantHeight(44)

let groupElement = GroupElement(configuration: configuration, elements: [...])
Using intrinsically sized form elements with padding
var configuration = GroupElement.Configuration()
configuration.style = .Grouped(backgroundColor: .whiteColor())
configuration.layout.edgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)

let groupElement = GroupElement(configuration: configuration, elements: [...])
Using a custom separator view
var configuration = GroupElement.Configuration()
configuration.separatorViewFactory = { (style, isBorder) in
    let separatorView = SeparatorView(axis: .Horizontal)
    separatorView.separatorInset = isBorder ? 0 : 20.0
    separatorView.separatorColor = .redColor()
    separatorView.separatorThickness = 2.0
    return separatorView
}

let groupElement = GroupElement(configuration: configuration, elements: [...])
Using a custom valiation error view
var configuration = GroupElement.Configuration()
configuration.validationErrorViewFactory = { message in
    let label = UILabel(frame: CGRectZero)
    label.textColor = .redColor()
    label.textAlignment = .Center    
    label.text = message
    return label
}

let groupElement = GroupElement(configuration: configuration, elements: [...])

BooleanElement

Boolean Element

BooleanElement displays a UILabel and a UISwitch that is bound to a Bool value.

toggle(title: "Boolean Element", value: self.booleanValue)

SegmentElement

Segment Element

SegmentElement displays a UILabel and a UISegmentedControl that is bound to a Segment<ValueType> value. ValueType is a type parameter the type of the value that the segment represents, which must be consistent for all of the segments. Each segment can have either a title or an image.

segments(title: "Segment Element", segments: [
    Segment(content: .Title("Segment 1"), value: "Segment 1"),
    Segment(content: .Title("Segment 2"), value: "Segment 2")
], selectedValue: self.segmentValue)

SpacerElement

SpacerElement displays an empty UIView of a fixed height. The view is configurable using all standard UIView properties (backgroundColor, etc.)

spacer(height: 20.0)

SegueElement

Segue Element

SegueElement displays a view with a UILabel and an optional UIImageView that triggers a configurable action when tapped.

StaticTextElement

Static Text Element

StaticTextElement displays static, non-editable text using a UILabel.

staticText("Welcome to the Forms Catalog app. This text is an example of a StaticTextElement. Other kinds of elements are showcased below.") {
    $0.textAlignment = .Center
    $0.font = UIFont.preferredFontForTextStyle(UIFontTextStyleFootnote)
}

EditableTextElement

EditableTextElement is an element that is bound to a String value and is capable of rendering several different controls for editable text. While these elements can be created by instantiating EditableTextElement directly, it is much simpler to use the convenience functions as shown in the code snippets below.

This element supports a number of different configuration options specified using the TextEditorConfiguration struct, including custom Return key behaviours (in addition to the standard tabbing behaviour between fields), as well as validation of the String value.

Text Field

Text Field

Displays a UITextField for a single line of editable text.

textField(value: self.emailValue, configuration: TextEditorConfiguration(continuouslyUpdatesValue: true), validationRules: [.email]) {
    $0.autocapitalizationType = .None
    $0.autocorrectionType = .No
    $0.spellCheckingType = .No
    $0.placeholder = "Text Field Element (Email)"
}

Text View

Text View

Displays a UITextView for multiple lines of editable text. The text view is actually an instance of PlaceholderTextView, which is a UITextView subclass that adds support for a placeholder string using the same API as UITextField.

textView(value: self.textViewValue) {
    $0.placeholder = "Text View Element"
}

Float Label

Float Label

Implements a native iOS version of the float label pattern. This concept is excellent for maintaining the context of the field label regardless of whether text has been entered in the field or not, unlike a traditional placeholder.

Float Label comes in two flavors: single line (instantiated using the singleLineFloatLabel function) and multi-line (instantiated using the multiLineFloatLabel function). The single line variant uses a UITextField as its underlying editor view, and the multi-line variant uses a PlaceholderTextView (UITextView subclass). In both cases, the underlying editor view can be accessed via the FloatLabel.textEntryView property inside the optional view configurator block.

singleLineFloatLabel(name: "Float Label Element", value: self.floatLabelValue)

ViewElement

ViewElement provides an easy way to wrap an existing view to create a one-off custom form element without any subclassing. It is used to implement the activity indicator element shown in the example application. It has to be initialized with a FormValue instance, which is then passed into the block that creates the custom view. However, if the view is not bound to a value (like in the activity indicator), you may simply pass a dummy value and ignore it inside the block.

customView(value: FormValue("")) { _ in
    let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
    activityIndicator.startAnimating()
    return activityIndicator
}

FormViewController

FormViewController is a UIViewController subclass that can be used standalone or embedded inside a parent view controller using view controller containment to display form content in your app.

let formViewController = FormViewController(elements: [
    singleLineFloatLabel(name: "Name", value: self.nameValue),
    singleLineFloatLabel(name: "Email", value: self.emailValue, validationRules: [.email]),
    singleLineFloatLabel(name: "Password", value: self.passwordValue) {
        $0.textEntryView.secureTextEntry = true
    }
])

addChildViewController(formViewController)
view.addSubview(formViewController.view)
formViewController.view.activateSuperviewHuggingConstraints()
formViewController.didMoveToParentViewController(self)

Testing

The framework is tested using a combination of snapshot tests via FBSnapshotTestCase and automated UI testing in Xcode.

License

This project is licensed under the MIT license. See LICENSE.md for more details.

Acknowledgements

Formalist uses StackViewController and FBSnapshotTestCase.

Comments
  • Complete refactor of text editing elements

    Complete refactor of text editing elements

    This is a much bigger refactor than I originally intended, but I think it's a huge improvement. Key points:

    • TextViewElement, TextFieldElement, and FloatLabelElement have been canned in favour of EditableTextElement, which heavily reduces the amount of code duplicated between the three. The view-specific bits have been pulled out into adapter classes that conform to the TextEditorAdapter protocol, and these can be used to instantiate an EditableTextElement
    • Instantiating an EditableTextElement involves some pretty verbose code, so there are convenience functions that initialize these for you (textField, textView, singleLineFloatLabel, and multiLineFloatLabel). These are actually much more readable than having *Element instantiated everywhere, so I think I will add convenience functions for all of the other element types in a follow-up PR as well
    • FloatLabel now supports using any view as the underlying text editor view, with built-in support for UITextView and UITextField. @vikmeup this is one of the features you needed to implement the "Show" button on the password field for the login screen.
    • The action for the return key is now customizable (Cmd+F "ReturnKeyAction"). @vikmeup another requested feature
    opened by indragiek 3
  • Use Carthage for dependencies

    Use Carthage for dependencies

    Removes the manually managed dependencies in the External directory and introduces Carthage instead.

    This PR is based off of #54 because I couldn't build the project in Xcode 9.1 without those changes.

    opened by klaaspieter 2
  • Number Formatting for Text EditableTextElement

    Number Formatting for Text EditableTextElement

    Implemented TextFormatter to allow setting pattern formatting for specific fields.

    TextEditorConfiguration has a property TextFormatter , if user set pattern: XXX-XXX-XXXX and replaceCharacter: replaceCharacter that field would be formatted that way while tapping.

    This ideal for formatting fields like phone number, social security, EIN, credit card number.

    Created FormNumberFormatter formatter that conform to Formattable, realized with @louoso that it's little tricky to support alphabetic values, for now it's gonna support just digits for formatting.

    I'm just typing number, it automatically adds - in between. text-formatting

    opened by vikmeup 2
  • CocoaPods support

    CocoaPods support

    So I'm not sure if the SeedStackViewController dependency will be problematic here, because the Formalist code still imports the module as StackViewController. I have yet to test this in an actual project.

    opened by indragiek 2
  • Add README and LICENSE

    Add README and LICENSE

    This is a lot of text, so please feel free to take your time when reviewing. I still have to add a section about installation via CocoaPods/Carthage and replace the [NAME] placeholder with whatever we're actually naming it.

    opened by indragiek 2
  • Support accessory toolbar for multiline float labels

    Support accessory toolbar for multiline float labels

    Change UITextViewTextEditorAdapter to honor TextEditorConfiguration.showAccessoryViewToolbar. If it's set to true the adapter will now show the toolbar when the text view becomes first responder.

    opened by klaaspieter 1
  • Allow subclassing of FormViewController

    Allow subclassing of FormViewController

    Allow FormViewController to be subclassed so that forms that take up the entire available space of a view controller's view don't have to do the addChildViewController dance. Subclasses can set up their own forms by calling the superclass' init(elements:) or init(rootElement) designated initializers.

    This also updates the example project with an example of a subclassed FormViewController.

    opened by klaaspieter 1
  • Make the different FormElements more discoverable

    Make the different FormElements more discoverable

    Make FormElements more discoverable by making them static functions on the FormElement type. This allows Xcode to offer suggestions for FormElements when someone types FormElement. or FormElement.group([.

    To accomplish this FormElement had to be changed from a protocol to a class intended for subclassing. This is because in Swift static members on a protocol can only be accessed through an implementation of the protocol. This would defeat the purpose because then users would have to type BooleanElement. and Xcode would suggest the boolean static function.

    opened by klaaspieter 1
  • Textfield padding

    Textfield padding

    Added container view for Float Label that allows us to add 12px from the top and bottom. So clickable area become => 44px

    Added multiLineFloatLabel to example View Controller

    screenshot 2016-07-18 11 52 40 screenshot 2016-07-18 11 55 16 screenshot 2016-07-18 11 52 11
    opened by vikmeup 1
  • Set default value for continuouslyUpdatesValue to true

    Set default value for continuouslyUpdatesValue to true

    Regarding this line of code https://github.com/seedco/Formalist/blob/master/Formalist/TextEditorConfiguration.swift#L47

    In most cases value needs to be updated right away, otherwise if form gets in the state where form.validate() is called, but user did not resign from textField or textView.

    opened by vikmeup 1
  • Two-way bindings

    Two-way bindings

    Currently, bindings between FormElement's and FormValue's are only one-way: a user updating the value of a form element causes the FormValue its bound to to be updated, but updating FormValue.value directly doesn't cause the form element's view to be updated accordingly. This PR implements bidirectional bindings for all elements that could benefit from it (BooleanElement, SegmentElement, EditableTextElement, and StaticTextElement).

    opened by indragiek 1
  • does some one can convert this to Swift4?

    does some one can convert this to Swift4?

    Or at least let us to have some kind of bridge, the dependencies on the SeedStackViewController are all broken. This library looks very interesting but its useless these days because its too much trouble having to recompile it again and fixing several compilation issues on the most upgraded environments. Thank you

    opened by jbarros35 0
  • Generalize customization of keyboard 'done' and 'next' buttons

    Generalize customization of keyboard 'done' and 'next' buttons

    Input fields can configure done & next action blocks associated with a KeyboardAccessoryToolbar's done & next buttons, but can currently only customize the title of the done button (https://github.com/seedco/Formalist/pull/64). These actions & titles are currently defined far apart from each other in a way that's specific to one use case; we'd like to allow more robust customization of these buttons and actions.

    opened by lsavino 0
Releases(0.7.1)
Owner
Seed
Seed - Mobile Business Banking
Seed
iOS validation framework with form validation support

ATGValidator ATGValidator is a validation framework written to address most common issues faced while verifying user input data. You can use it to val

null 51 Oct 19, 2022
Elegant iOS form builder in Swift

Made with ❤️ by XMARTLABS. This is the re-creation of XLForm in Swift. 简体中文 Overview Contents Requirements Usage How to create a Form Getting row valu

xmartlabs 11.6k Jan 1, 2023
The most flexible and powerful way to build a form on iOS

The most flexible and powerful way to build a form on iOS. Form came out from our need to have a form that could share logic between our iOS apps and

HyperRedink 32 Aug 15, 2022
SherlockForms - An elegant SwiftUI Form builder to create a searchable Settings and DebugMenu screens for iOS

??️‍♂️ SherlockForms What one man can invent Settings UI, another can discover i

Yasuhiro Inami 98 Dec 27, 2022
APValidators - Codeless solution for form validation in iOS!

APValidators is a codeless solution for form validation. Just connect everything right in Interface Builder and you're done. Supports really complex and extendable forms by allowing to connect validators in tree.

Alty 131 Aug 16, 2022
Declarative data validation framework, written in Swift

Peppermint Introduction Requirements Installation Swift Package Manager Usage Examples Predicates Constraints Predicate Constraint Compound Constraint

iOS NSAgora 43 Nov 22, 2022
Former is a fully customizable Swift library for easy creating UITableView based form.

Former is a fully customizable Swift library for easy creating UITableView based form. Submitting Issues Click HERE to get started with filing a bug r

Ryo Aoyama 1.3k Dec 27, 2022
ObjectForm - a simple yet powerful library to build form for your class models.

ObjectForm A simple yet powerful library to build form for your class models. Motivations I found most form libraries for swift are too complicated to

jakehao 175 Nov 2, 2022
Some cells to Form a Pod

CellsGao Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installation CellsGao

null 0 Nov 2, 2021
Custom Field component with validation for creating easier form-like UI from interface builder.

#YALField Custom Field component with validation for creating easier form-like UI from interface builder. ##Example Project To run the example project

Yalantis 476 Sep 1, 2022
SwiftyFORM is a lightweight iOS framework for creating forms

SwiftyFORM is a lightweight iOS framework for creating forms Because form code is hard to write, hard to read, hard to reason about. Has a

Simon Strandgaard 1.1k Dec 29, 2022
A framework to validate inputs of text fields and text views in a convenient way.

FormValidatorSwift The FormValidatorSwift framework allows you to validate inputs of text fields and text views in a convenient way. It has been devel

ustwo™ 500 Nov 29, 2022
XLForm is the most flexible and powerful iOS library to create dynamic table-view forms. Fully compatible with Swift & Obj-C.

XLForm By XMARTLABS. If you are working in Swift then you should have a look at Eureka, a complete re-design of XLForm in Swift and with more features

xmartlabs 5.8k Jan 6, 2023
Boring-example - Using boring crate from iOS application

BoringSSL example Using boring crate from iOS application. Checkout git clone gi

Alexei Lozovsky 0 Dec 31, 2021
iOS Validation Library

Honour Validation library for iOS inspired by Respect/Validation. Validator.mustBe(Uppercase()).andMust(StartsWith("F")).validate("FOOBAR") ❗ If you w

Jean Pimentel 55 Jun 3, 2021
GrouponHeader - iOS TableView Header Animation, Swift/UIKit

GrouponHeader Description: iOS TableView Header Animation Technology: Swift, UIK

James Sedlacek 8 Dec 15, 2022
Meet CRRulerControl - Customizable Control for iOS

Customizable component, created by Cleveroad iOS developers, is aimed at turning a simple ruler into a handy and smart instrument

Cleveroad 112 Oct 2, 2022
Declarative form validator for SwiftUI.

SwiftUIFormValidator The world's easiest, most clean SwiftUI form validation. SwiftUIFormValidator A declarative SwiftUI form validation. Clean, simpl

Shaban Kamel 42 Dec 13, 2022
Carbon🚴 A declarative library for building component-based user interfaces in UITableView and UICollectionView.

A declarative library for building component-based user interfaces in UITableView and UICollectionView. Declarative Component-Based Non-Destructive Pr

Ryo Aoyama 1.2k Jan 5, 2023
StyledTextKit is a declarative attributed string library for fast rendering and easy string building.

StyledTextKit is a declarative attributed string library for fast rendering and easy string building. It serves as a simple replacement to NSAttribute

GitHawk 1.2k Dec 23, 2022