The Big Entity Problem

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.

The Solution. TLFormView

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.

August 2018: Please note that this post was written for an older version of Swift. Changes in the code might be necessary to adapt it to the latest versions and best practices.

@implementation ViewController - ( void ) viewDidLoad { [super viewDidLoad]; TLFormView * formView = [[TLFormView alloc] initWithFrame:self.view.frame]; formView.formDelegate = self; formView.formDataSource = self; [self.view addSubView:formView]; ... }

This creates a TLFormView instance and set the controller as formDelegate and formDataSource .

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.

The 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:

- (NSArray * ) fieldNamesToShowInFormView: (TLFormView * )form { return @[ @"user_name" , @"photo" , @"age" , ] ; } - (TLFormField * ) formView: (TLFormView * )form fieldForName: (NSString * )fieldName { Class fieldClass; NSString * title; id value; //'userModel' is an object that holds the user information if ([fieldName isEqualToString: @"user_name" ]) { fieldClass = [TLFormFieldSingleLine class ]; title = @"User Name" ; value = userModel.name; } else if ([fieldName isEqualToString: @"photo" ]) { fieldClass = [TLFormFieldImage class ]; title = @"Photo" ; value = userModel.photoUrl; } else { fieldClass = [TLFormFieldSingleLine class ]; title = @"Age" ; value = userModel.age; } return [fieldClass formFieldWithName:fieldName title:title andDefaultValue:value]; } - (NSArray * ) constraintsFormatForFieldsInForm: (TLFormView * )form { return @[ @"V:|-[photo(==230)]-" , @"H:|-[photo(==420)]-|" , @"V:[photo]-[user_name(>=44)]-" , @"H:|-[user_name]-|" , @"V:[age(==user_name)]-|" , @"H:|-[age]-|" ] ; }

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:

iPhone vertical layout

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:

- (NSArray * ) constraintsFormatForFieldsInForm: (TLFormView * )form { //For iPhone we want a vertical layout like we get on a UITableView if (isIPhone) { return @[ //Place the photo on the top with the size 230x420 @"V:|-[photo(==230)]-" , @"H:|-[photo(==420)]-|" , //Now place the user name below with a height of 44p @"V:[photo]-[user_name(>=44)]-" , @"H:|-[user_name]-|" , //And the age last with the same height than the user_name @"V:[age(==user_name)]-|" , @"H:|-[age]-|" ] ; //For anything else we will place the image on the top left and the rest of the fields to the right } else { return @[ //Place the photo on the top left with the size 230x420 @"V:|-[photo(==230)]" , @"H:|-[photo(==420)]" , //Now place the user name to the right with a height of 44p @"V:|-[user_name(>=44)]" , @"H:|-[photo]-[user_name]-|" , //And the age below and also to the right of the image with the same height than the user_name @"V:[user_name]-[age(==user_name)]-|" , @"H:|-[photo]-[age]-|" ] ; } }

Here is how it looks on an iPad portrait:

iPad portrait

What’s next?

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:

pod try TLFormView

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.