This document provides an overview of the shinobiforms control. It describes the features of the control and its associated concepts.
shinobiforms provides a quick and effective way to capture your user’s data in an iOS application. Once your user has entered their data into the fields in a form, the control allows you to access that data.
If you simply want to get up and running, follow the
Form model objects can exist independently of a view, although model objects will need a view to represent them if you are to collect data from the users of your application (see Views). The fact that model objects can exist without views means you can store them, create them, and pull information from them without caring about the view hierarchy.
ShinobiForm is a model object of a form. A form is a collection of fields that are conceptually linked composing a single unit of input for submission. The model needs to be represented by a view so it can be added to your view hierarchy for user interaction (see SFormView). A form has multiple
SFormSection model objects that represent groupings of
SFormFields within the form.
ShinobiForm *form = [ShinobiForm new]; SFormSection *section = [SFormSection new]; form.sections = @[section];
ShinobiForm supports submitting its data to a delegate. To do this, set the
delegate property on the form which can be an instance of any class which adopts the
SFormDelegate protocol. Then, call the
submit method on the form. This method will validate the form and then notify the delegate.
To add a submit button to your form see the section Adding a Submit Button.
SFormSection is a model object of a section. A section is a collection of fields that are grouped and laid out separately to fields in other sections. The model needs to be represented by a view so it can be added to your view hierarchy for user interaction (see SFormSectionView).
SFormField *field = [SFormTextField new]; section.fields = @[field];
SFormField is a model object of a field. The model needs to be represented by a view so it can be added to your view hierarchy for user interaction (see SFormFieldView).
A field object holds a
value of a specific type. This type depends on the type of field you are using - check the specific fields documentation for further information. Setting the value on a field will cause the validators contained with the field’s
validators array to validate the field. This will in turn update the field’s
isValid status. Any failing validators will be added to the
currentlyFailingValidators array providing an easy way to grab error messages from the failing validators.
For a list of the available field model subclasses see the documentation for SFormField.
SFormField class has a
validators property which holds objects conforming to the appropriate validator protocol (
SFormChoiceFieldValidator etc.). These objects are responsible for validating the contents of a field’s
value property. An
SFormField will validate whenever the
value property changes.
You can specify the type of validation you want on your field by assigning validators like so:
SFormTextField *field = [SFormTextField new]; field.validators = @[[SFormTextFieldNotEmptyValidator new]];
We provide a number of validators out of the box which can be initialized either directly as shown above, or through our validation utility classes.
field.validators = @[[SFormTextFieldValidators notEmpty]];
We have created a code sample which demonstrates this: ValidatingTextField.xcodeproj
You can create validators by directly instantiating the validator classes but we recommend using the validator utility class for each field type. These validators provide a ‘menu’ of our provided validators, as well as convenience methods for common validation use cases. Below is a list of the validation utility classes.
SFormTextFieldValidators: A validation utility class for
SFormNumberFieldValidators: A validation utility class for
SFormDateFieldValidators: A validation utility class for
SFormBooleanFieldValidators: A validation utility class for
SFormPickerFieldValidators: A validation utility class for
SFormChoiceFieldValidatorsA validation utility class for
You may find you need to validate a field by comparing it to the value of another field. For example, a ‘maximum’ field where the value must be larger than a ‘minimum’ field. To make these validation cases easier, we’ve provided validators that do just this.
To validate a field’s input against another field, simply create the validator by providing the field to compare against and the required result, then add the validator to the field.
SFormNumberField *minPriceField = ...; SFormNumberField *maxPriceField = ...; SFormNumberFieldComparisonValidator *lessThanMax = [SFormNumberFieldValidators compareWithNumberField:maxPriceField validResult:SFormComparisonRuleLessThanOrEqual]; minPriceField.validators = @[lessThanMax]; SFormNumberFieldComparisonValidator *greaterThanMin = [SFormNumberFieldValidators compareWithNumberField:minPriceField validResult:SFormComparisonRuleGreaterThanOrEqual]; maxPriceField.validators = @[greaterThanMin];
SFormNumberFieldValidators utility class provides a much easier way to validate a minimum and a maximum field - the above code snippet can be simplified down to the following.
SFormNumberField *minPriceField = ...; SFormNumberField *maxPriceField = ...; self.minMaxGroup = [SFormNumberValidators minMaxGroupForMinNumberField:minPriceField maxNumberField:maxField];
SFormFieldGroup can be used to ensure that when one field updates other fields also re-validate - useful when one field is validating against the contents of another. (We use field groups internally to implement minimum & maximum field validation as seen on the validator utilities.) If one field in the validation group validates, every field in the group will also validate.
To set up a validation group all you need to do is create one, add some fields.
SFormFieldValidationGroup *validationGroup = [SFormFieldValidationGroup new]; validationGroup.fields = @[field1, field2]; self.myValidationGroup = @[validationGroup];
Note that you need to keep a strong reference to your validation groups.
Without a view representing model objects, your forms won’t be able to collect data from your users. The view is what your application’s users will be interacting with.
Each model object has a subclass of
UIView to visually represent it.
ShinobiForms have SFormView.
SFormSections have SFormSectionView.
SFormFields have SFormFieldView subclasses.
These views observe the model objects and ensure that they keep in sync when the model updates. They also make sure that they keep the model up to date when your users enter data into the views. (Note that the model will not update to reflect programatic changes on the view.)
The easiest way to set up a form is to use one of our provided view builders (see View Builders). However, if you have a view you have created, you can easily connect it up with your model object by assigning the model object to the view and adding an appropriate converter (see Converters). The view takes care of all observation and synchronizing.
SFormTextField *model = [SFormTextField new]; SFormTextFieldView *view = [SFormTextFieldView new]; // Add a converter to the view. ... view.model = model;
Note that when assigning a model to a view, the model will synchronize with any values already set on the model, as well as any values set on the model from then onwards. For example, the two following code snippets are functionally equivalent.
// Snippet 1 SFormTextField *model = [SFormTextField new]; SFormTextFieldView *view = [SFormTextFieldView]; // Add a converter to the view. ... view.model = model; model.value = @"Hello World!"; // Field updates to display the model's new value. // Snippet 2 SFormTextField *model = [SFormTextField new]; model.value = @"Hello World!"; SFormTextFieldView *view = [SFormTextFieldView]; // Add a converter to the view. ... view.model = model; // Field updates to display the model's value.
View builders provide a simple way to build a view hierarchy for any of your model objects. All you need to do is provide your model to one of these builders and it will spit out a ready to use view hierarchy. You will have already seen this in the Creating a Simple Form section. An example of building a form view for a form model is given below.
SFormView *formView = [[SFormFormViewBuilder new] buildViewFromModel:self.form];
When providing a model object to one of the previously mentioned view builders, the view builder will carry out three steps.
Warning: In the case of the
SFormViewBuilder and the
SFormSectionBuilder the views will be built recursively downwards. For example, passing a form model through an
SFormViewBuilder will create views for the form model’s section model, and the section model’s field models.
Warning: Modifying the
sections array will cause any section views on the form to be replaced.
To add a submit button to your form, all you need to do is set a
UIButton as your
submitButton property. It will automatically have the parent form added as a target, calling
submit on touch up inside. We also provide a UIButton subclass,
SFormSubmitButton, which is styled to match our default form style.
SFormSubmitButton *button = [SFormSubmitButton new]; [button sizeToFit]; formView.submitButton = button;
For advice on positioning the submit button see Layout.
For an example of using a submit button in a form, take a look at the SubmitForm.xcodeproj sample project.
SFormSectionView is a visual representation of a
SFormSection model object. The
SFormSectionView observes changes to the
SFormSection object assigned to its
For a list of properties the
SFormSectionView observes on its model, check the
Warning: Modifying the
fields array will cause any section views on the form to be replaced.
SFormFieldView is a visual representation of a
SFormField model object. There are a number of
SFormFieldView subclasses provided to represent different field model object subclasses. The
SFormFieldView is a
UIView subclass that is made up of the following components:
label: This is a
UILabelwhich can be used as the title of your field.
UILabelcan be used to indicate to the user that the field is required.
UIViewallowing data entry.
UILabelthat can display an error message upon failed validation.
For a list of the available view subclasses see the
There may be times when you want a field to be disabled. To do this you can simply set the field view’s enabled property to
fieldView.enabled = NO;
This will change the field to look inactive, giving it a faded appearance as well as disallowing user input.
SFormFieldView observes changes to the
SFormField object assigned to its
model property. For example,
SFormFieldView observes the
value property on it’s assigned
model. Updating the model’s
value property will update the value displayed in the field as shown below.
SFormTextField *model = [SFormTextField new]; SFormTextFieldView *view = [SFormTextFieldView new]; view.model = model; model.value = @"Hello World!";
The above snippet will change the
text value of the field view’s
UITextField to “Hello World!”.
There are a few properties that the field view observes, check the
SFormFieldView’s documentation (and the documentation of any
SFormFieldView subclasses you are using) for a full list.
The type of a field view’s input can differ from the type or format of it’s model’s
value property - this is where the
converter plays its role. A converter is responsible for converting between these two differing types or formats and is an object conforming to
An example of where the type is the same, but the format may differ is an
SFormTextFieldView with an underlying
SFormNumberField. In this case the user may input any characters offered by the pop-up keyboard into the
inputElement of the
SFormTextFieldView, but the
SFormNumberField needs to receive an
NSString for its
value property that only contains numbers or valid grouping/decimal separators. This requirement is often described as an input mask, and can be achieved with a converter.
To set up a view without using the builders you need to first create your own view.
SFormTextFieldView *view = [SFormTextFieldView new];
Then we connect up the model as described in Connecting the View and Model.
view.model = [SFormNumberField new];
You then need to assign an appropriate converter. In this example we’ll need a converter that masks non-numeric characters in an
NSString on the view side and passes this to the model side. You could write your own, by implementing a class that conforms to
SFormFieldModelViewConverter, but we’re going to use the provided
SFormNumberFieldConverter which already has this functionality.
view.converter = [SFormNumberFieldConverter new];
You now have a field view that takes text input of any format, a converter which strips non-numeric characters (aside from grouping/decimal separators), and a model that will receive only numeric strings from the converter.
SFormFieldModelViewConverter defines two methods:
viewValueFromModel:- this method is responsible for converting from the model’s type/format to the type/format needed by the view.
modelValueFromView:- this method is responsible for converting from the view’s type/format to the type/format needed by the model.
By implementing these two methods you have ultimate power over view/model compatibility. Using different converters, or even converters you’ve written yourself, you can create new field combinations using the model objects and views we provide out of the box - you can even combine them with your own custom views!
We have created a code sample which demonstrates a converter: Converter.xcodeproj
SFormView’s layout object is an object conforming to
SFormSectionViews layout object is an object conforming to
SFormFieldView’s layout object is an object conforming to
This layout is delegated to for the view’s
Assigning an object to this layout property provides an easy way to change the layout of a view without subclassing. To change a view’s layout you can create your own concrete implementation of the relevant protocol and assign it to the view’s
layout. For information on our out of the box layouts, please see the layout protocols for a list of concrete implementations (
It is possible to write your own layout behaviors by creating classes that conform to the above layout protocols. These objects can them be assigned to the relevant view’s
ShinobiForm *formView = [ShinobiForm new]; formView.layout = [MyTotallyAwesomeLayout new];
There are two other alternatives ways to lay out a field.
SFormTextFieldView *view = [SFormTextFieldView new]; view.layout = nil; view.frame = CGRectMake(0, 0, 300, 50); view.inputElement.frame = CGRectMake(0, 0, 200, 50);
Adding the form to the scroll view is done exactly the same as you would with any other view. However, shinobiforms provides a useful class that increases the usability of a form in a scroll view,
SFormScrollViewManager. The SFormScrollView manager modifies a given scroll view in response to keyboard events. All you need to do is set up your scroll view manager like so.
self.scrollViewManager = [[SFormScrollViewManager alloc] initWithScrollView:myScrollView];
When the keyboard appears, the correct amount of padding will be added to the scroll view’s content inset so that your users can scroll to the bottom of your scroll view to see fields that would otherwise be covered by the keyboard.
For an example of using the scroll view manager, take a look at the FormInScrollView.xcodeproj sample project.
You can add a toolbar above the keyboard whilst editing a
SFormField by assigning a
UIView conforming to the
SFormNavigationToolbar protocol to your form view’s
keyboardToolbar property. By default this toolbar will be an instance of
SFormToolbar which provides two buttons on the toolbar that allow users to easily navigate between fields.
When fields are added to the form they will have their
inputAccessoryView set to this the value of
keyboardToolbar. If you’d like to remove the toolbar you can simply assign nil to the
formView.keyboardToolbar = nil;
As mentioned previously, the default keyboard toolbar is an
SFormToolbar. This toolbar will provide two buttons, a previous button (a minimal arrow pointing to the left) and a next button (a similar arrow pointing to the right). These arrows allow users to navigate to the field previous or next to the currently selected field. If you are at the very first or last field, the previous or next button will be disabled respectively.
UIToolbar, for basic customization you can simply modify or add
UIBarButtonItem objects in the toolbar’s
items array. If you’re looking for heavier customization and you don’t want to hook into our built in navigation functionality then you can directly replace the
keyboardToolbar property on your form. However, if you’re hoping to keep our navigation functionality then you’ll need to understand a little bit about how our toolbar communicates with the form and vice versa.
fieldNavigator gives you an object you can send messages to, telling the form to focus on a new field. As you can see, the object passed into the method
setFieldNavigator: conforms to the protocol
SFormNavigator which defines three methods,
focusNextField (these methods will do nothing if there is no previous/next field) and
When a new field view becomes first responder, the toolbar will have the method
fieldView:becameFirstResponderHasPrevious:hasNext: called on it. This method tells the toolbar what field was selected, and whether there are any fields previous or next to it. For example, we could display information specific to the selected field somewhere on our toolbar, or enable/disable the next and previous buttons as we get to the beginning/end of the form.
It is not currently possible to write the view part of your form in XML, but the
SFormFormViewBuilder can help you to easily generate a view hierarchy to represent the
ShinobiForm output from the parsing of XML. See View Builders for more information on this.
See the FormFromXML.xcodeproj sample for an example of how a form can be constructed from XML.
The best way to ensure you are writing XML that our API can parse is to adhere to the XSD (XML Schema Definition) file (ShinobiForms_XSD.xml) that is embedded in our ShinobiForms.framework.
See the FormMeta.xml file in the FormFromXML.xcodeproj sample for an example of a valid XML file that can be parsed to a
The following is a list of XML elements and the ShinobiForm objects they map to:
Our API handles XML as
NSStrings. You can parse your xml via the following:
NSString *xml = //get your xml as an NSString ShinobiForm *form = [[SFormXMLParser new]formFromXml:xml xmlEncoding:xmlEncoding xsd:nil xsdEncoding:0];
As seen in the Parsing XML Forms code example, the method for producing a
ShinobiForm from XML (
formFromXml:xmlEncoding:xsd:xsdEncoding:) has an XSD parameter. This is used to validate that the XML meets the schema definition before parsing begins. Passing
nil for this parameter results in us falling back to using the ShinobiForms_XSD.xml file in your application’s main bundle.
It is possible to validate XML without then parsing it into a
NSString *xml = //get your xml as an NSString BOOL isValid = [[SFormXMLParser new]isValidXml:xml xmlEncoding:xmlEncoding xsd:nil xsdEncoding:0];
shinobiforms uses a strings file to allow you to localize the strings used within your forms. The form looks for a strings file named ShinobiForms.strings within your application’s main bundle. You can add this to your main bundle by following Adding ShinobiForms Resources. Once this is done you should then be able to add extra strings for the locales you wish to support.
You can add ShinobiForms resources to your application’s bundle. You can do this by dragging the resource/file, found within your ShinobiForms.framework bundle, into your project’s navigator.