Easy And Flexible Forms In iOS With Interface Builder

This post looks at how to easily design future-proof data entry forms in iOS with Interface Builder. It includes a demo app written in Swift.

There are some libraries written to help with creating forms, but sometimes you want to quickly create a typical form for data entry without the overhead of finding and integrating a library, so this is what I’ll walk through.

This tutorial and demo app will go beyond the basics, and illustrate a few important techniques for ensuring that your forms are very functional and flexible.

  1. We’ll build in a UIScrollView so that portrait and landscape are supported, and the addition of new fields in the future will be simple.
  2. We’ll adjust the scroll view when the keyboard opens.
  3. We’ll make it totally easy to change the stacking of labels and text fields, for those times where the client decides they want to move things around.

I’ll start by creating a new Single View Application project, and call it EasyFormLayout.

The first thing to do is add a UIScrollView under the main View. By doing this, we achieve crucial technique #1 that I mentioned above. If the fields won’t fit on the screen in either orientation, the scroll bar will show up automatically. Even if it isn’t needed initially, in the future if more fields are required, everything is in place to quickly add them.

Let’s say we want OK and Cancel buttons at the bottom of the screen. To accomplish that, we’ll add a horizontal UIStackView, and put the two buttons in it. To position the buttons nicely even though they have different sized text, I set the Distribution to Fill Equally, instead of the default value of Fill.

Constraints are needed for the UIScrollView and the UIStackView to constrain relative to the main View. I’ll define them for Leading and Trailing on the UIScrollView and UIStackView, then add a Top constraint for the UIScrollView, Bottom constraint for UIStackView, and then a Vertical Spacing constraint between those two views. I specified Constant values of 10 on the Top, Bottom, and Vertical Spacing constraints. Here is what it looks like after these steps.

This is setup so that there is a specific area within which to add controls that are subject to scrolling, while the OK and Cancel buttons will always be on screen regardless of scrolling.

The next step is to add one single child view inside the UIScrollView, which will hold any components we wish to have for data entry.

Normally, you would see tutorials talking about a Content View. This is necessary if you want to have multiple controls inside the UIScrollView. In our case, the root of our data collection components will be a UIStackView. We could just add that and not bother with a UIView for the Content View. However, I want to inset the data collection area by 20 all around, so having the Content View will facilitate this. I’ll explain why shortly.

Let’s go ahead and add the UIView and name it Content View so it is clear. We’ll define Leading, Trailing, Top, and Bottom constraints, with Constant values of 0. We’ll then add the UIStackView inside the Content View, with Constant values of 20 for the Leading, Trailing, Top, and Bottom constraints.

Here is what we have now after adding the UIView (Content View) and the UIStackView along with the constraints.

At this point the Scroll View does now know what it’s content size should be, because we have not added any controls with intrinsic content sizes. We could also just provide height and width constraints, but that’s not what we want to do. Here are the auto layout errors that are reported.

Once we add just one row of our data entry components, these errors will be cleared. We will do so by adding one UIStackView for each row of data, and each one of those will contain a UILabel and UITextField. Let’s add the first row so we can see what the impact is. I’ll add a horizontal UIStackView, with a Spacing value of 20, along with the UILabel and UITextField. Other than that, the default values for the UIStackView are fine. I’ll set the text for the UILabel to be Name.

You’ll see now that the auto layout errors have disappeared. However, the intrinsic sizes of the label and text field are being used, so that means the UIScrollView believes it’s content size to be very small, especially with no text in the UITextField.

In order to make sure the available space is used to make the screen look nice, all we need to add is an Equal Widths constraint between the Content View and the main View. This will tell auto layout to always keep the Content View the same size as the main View, and therefore it will expand the UITextField to fill the space available. This is the reason why we wanted the Content View in this app, versus just having the UIStackView as the root of the UIScrollView. With the Equal Widths constraint added, this is what it looks like.

With only one row of data entry setup, clearly we won’t be seeing scroll bars, even in landscape orientation. Let’s add more rows just like the first one, and label them as Website, Post Topic, Language, Month, Previous Topic, Xcode Version, and iOS Version. The Spacing for the root UIStackView that was added earlier needs to be updated to a value that separates all the rows of data vertically, so I have set it to 20. The result looks as follows.

You can see that the text fields are not sized the same. If we had designed this to put all the text fields in one UIStackView, and the labels together in one, the sizing would be correct. However, by having each row in a single UIStackView, we are keeping our options open to be able to easily adopt a UI paradigm where the field labels are above the text fields, which is very common. I’ll illustrate how simple it is to do that towards the end of this post.

With this layout, we can easily size the labels and text fields to be consistent, by adding an Equal Widths constraint between the Name and Website labels, and then repeating this from Name to all other labels. In fact, this is a technique that Apple suggests in their Auto Layout Cookbook.

Once I’ve added those constraints, all of the labels and text fields are nicely aligned vertically. The one thing I also added for all of the UITextFields, was to lower the Horizontal Compression Resistance to 249. I did that so that they have a resistance lower than the labels, and therefore the labels will not get cut off.

Before I run the app, I’m also going to change the Background of the main View to a light shade of gray, and set the Background of the Content View to Default, which is Clear Color. Running the app now, in an iPhone 6 simulator, we can see the following screens in portrait and landscape.

We can see that scrolling isn’t needed for portrait, but when switching to landscape, scrolling is active since there isn’t enough space for displaying all the fields. Adding all the content inside a UIScrollView has paid off already right from the start.

There are some other aspects we need to consider. First, when the keyboard opens, if the field being edited is covered by the keyboard, we need to auto scroll to show that field. In addition, we should close the keyboard when the user clicks outside the text fields. Let’s handle both of those scenarios before we wrap up this tutorial and demo app.

To setup the auto scroll, as described on Hacking With Swift, first add the following inside viewDidLoad.

Then implement the adjustForKeyboard method.

Starting in iOS 9, it is no longer necessary to remove observers, since notifications will not be sent to observers that have been deallocated.

That takes care of adjusting the view when the keyboard opens. The final piece of this demo app is to close the keyboard when the user clicks outside the text fields. First, we need to add a UITapGestureRecognizer in the viewDidLoad method.

Then we just add the dismissKeyboard method.

You can run the app now, rotate to landscape mode, and when one of the bottom fields is clicked, scrolling happens automatically to scroll up to the text field being edited. Additionally, when editing any text field, clicking outside it will hide the keyboard.

I mentioned earlier how easy it is to change the UI paradigm so that each label is above it’s respective text field. We can do this by first removing the Equal Widths constraints we added from the Name UILabel to the other 7 labels. I’ve shown the set of constraints below that I removed for this UI change.

Then, we just change each UIStackView that contains a label and text field, so that the Axis is Vertical instead of Horizontal, and the Spacing is reduced to 5. Here is what it looks like in Interface Builder after I’ve just changed the last UIStackView.

That was definitely easy to change. Of course with this layout, even in portrait, we need the scrolling to be in place for some devices, so it is a good future-proofing strategy for such a screen. I did not include these last changes in the demo app, but you can try them on your own.

Update 19 Sept 2017. I was asked in a comment about adding a header to the form. You can see my reply below in the comments, so I’ve also updated the project in GitHub to include a simple header that sticks at the top when you scroll. Note that you need to rotate to landscape on some device sizes to activate the need to scroll. You can see a screenshot below of the updated demo that includes the header. Thanks to the original poster, S Tunji Turner, for asking the question!


The demo app is here in GitHub.

Leave a Comment:

Add Your Reply