Thu, May 14, 2015
As an iOS developer with a few of years of experience (I started back when the iPhone 3G was born) I have faced the same issues many times in lots of different scenarios. One of the common ones is handling large data entities: objects with many fields with complex types that must be visible and editable by users. This imposes many challenges in the UI/UX due the limited capabilities of a mobile device and complexity increases very quickly if you think about the now growing iOS devices family.
Creating a simple and reliable solution adapted to many different platforms becomes very difficult in these cases.
For example, suppose for a second that our app needs to handle (show and edit) a detailed user profile with ten fields. In the case of phones (iPhone, iPhone 6 Plus) we will probably pick a vertical layout like the one we get with
UITableView. For the tablets (iPad mini, iPad) we could use the same approach but we would certainly be wasting most of the screen space and giving our users an uncomfortable experience.
The problem increases if for example we don't have a fixed set of fields to show so the implementation must generate the form dynamically based on a remote API call, let alone screen rotation.
As you can see this is not a trivial issue and all iOS developers face this sooner or later. Because this is such a common problem (and an annoying one to implement) there are many "generic forms" implementations out there. Most of them solve this problem by extending
UITableView and providing some mechanism to configure form fields (aka rows of the table view). The bright side is that you get a nice form with pre-build fields that cover 80% of the cases (date field, number field, etc). The down side is that you can't change the layout because it's a
UITableView so you are forced to use a vertical list layout. In some cases you can't even use your own custom fields or change the ones shipped, so make your app adaptable to all platforms (or styles) is not possible.
To overcome all these issues we developed
TLFormView. A generic form that doesn't constraint layout or field types. It provides a default behavior that makes it work as most of the rest of the solutions, but it's in the extra configurations where it shines and stands apart.
TLFormView is an extension of
UIScrollView with a simple mechanism to handle its fields.
TLFormField represents a field of
TLFormView and it's just an extension of
UIView so you can place anything you want as a form field as long as it extends this class. The form also provides some other cool features like: in place help for each field, conditional visibility for a field with an
NSPredicate based on other field's value, in place editing mode switch and more.
As an example let's implement a extremely simple user profile with three fields: user name, a photo and an age field. First we need to setup an instance of
TLFormView, I will code it in this example, but it can be all done from the Interface Builder.
This creates a
TLFormView instance and set the controller as
The form delegate is a reference to any object that implements the
TLFormViewDelegate protocol. This delegate receives a message every time a field value changes with the selector
formView:didChangeValueForField:newValue: and when a field is selected with
formView:didSelectField:. This lets the controller react to events on the form.
formDataSource property on the form is a reference to any object that implements the
TLFormViewDataSource protocol. The implementation of this protocol is responsible of configuring the form, determining what fields to show and their layout. Here is the implementation of this protocol for a simple vertical layout for any devices:
Ok, there are many things to explain here: First
fieldNamesToShowInFormView: implementation returns an
NSArray of strings with the names for each field. A field name is an id that identifies the field in the form's context. Then
formView:fieldForName: is called by the form one time for each field name in the
NSArray returned earlier. The implementation here returns one of the standard fields provided out of the box, those are basically an image and two single-lined fields. All the fields are constructed with the
TLFormField default constructor. For a complete list of the available fields check the docs in our repo.
The last method in the data source is
constraintsFormatForFieldsInForm:. This method returns an array of constraints that define layout using Auto Layout Visual Format. Here the constraints place fields in a vertical layout. The final result is this:
Now, suppose we want a different layout for the iPad. The only thing we need changed is the implementation of
constraintsFormatForFieldsInForm: to check for current devices, like this:
Here is how it looks on an iPad portrait:
This concludes the first part of the "The form of the future" futuristic series of posts. As you may note we left out some of the more intriguing features like "conditional visibility" or the "in place help", not to mention the near impossible "edit mode switch". So if you want to know more, keep in touch to find how these features work in the next post. In the mean time, feel free to check the project repo at GitHub or the example project with cocoa pods:
Keep in mind that none of the above will be even close as fun as the interesting features we are going to talk about in the next blog post 😉
See you in the future.
Edit: the second part is already available here.