The Ultimate Course to Making Professional iOS Apps – Lesson 4

The Ultimate Course to Making Professional iOS Apps

Lesson 4: showing information to the user and enabling interaction through views, outlets and actions

In the previous lesson we have explored the view controller layer of the MVC pattern. We have seen how view controllers represent the screens of an app and how containers coordinate different view controllers. We have also seen how we can express the whole flow of an app in a storyboard, even without having the whole interface.

In this lesson we will move to the last layer of the MVC pattern: the views. We will create the UI of our app and connect it to our code.

Laying out the user interface and connecting it to code

The top layer in the MVC pattern is the view layer. The responsibilities of views in iOS are:

  • displaying the user interface on the screen
  • showing data to the user
  • allowing user interaction

Notice how there is no mention about the model of the app or of view controllers. Views are generic objects that are completely separated from the logic of your app. They know nothing about all the other classes in your app. They don’t know anything about the model and even about view controllers. This is a mistake I have seen many times developers make.

In general views are rectangular areas on the screen. They might seem to have different shapes because some of them perform complex drawings, but their area is always rectangular. Views can be placed inside of each other and be organized in a hierarchy. At the top of this hiearachy there is the view of the view controller. Inside this view which we lay out all the user interface for a particular screen. Views are positioned on the screen by expressing the origin and the size of their rectangle, called frame.

This sounds pretty simple, but it is complicated by the fact that different iOS devices with different screen sizes exist. We cannot simply set a fixed size and position for our views since they have to adjust to the size of the screen.

For this reason Apple introduced a system called Auto Layout. In Auto Layout you don’t alter the frames of the views. You instead express relationships between views through constraints. When your app runs, the Auto Layout system uses your constraints to calculate the final frames of all the views on the screen.

Auto Layout is a very complex system which allows us to create very complex dynamic UIs. Most of the time though we don’t need all its power and we can create UIs using a small subset of Auto Layout’s functionality.

When all your UI is in place, you need to connect it to your code, to be able to manipulate views and respond to user action. There are two ways to connect the UI to code in iOS:

  • Outlets, which are properties in objects which create a reference to any view in a storyboard. Through outlets you can configure views, pass data to them or read data from them.
  • Actions, which are methods that allow to connect controls to code. When a user interacts with a control, for example a button, the action is executed.

Simplifying Auto Layout with Stack Views

In iOS we can build the UI of our apps in a storyboard, using Interface Builder. In Interface Builder we can place all kinds of UI elements in our view controllers’s view. iOS offers a lot of different standard views you can use in your apps.

Interface Builder also allows you to express Auto Layout constraints to position views. Sometimes though, for very complex UIs, you need to manage constraints in your code.

Most iOS UIs can be arranged in grids. For this reason Apple introduced a special type of view, the stack view. This view creates all the needed layout constraints itself. In this way, we can just add our views to a stack view and configure a few parameter detailing how to arrange subviews. We can, for example, arrange views vertically or horizontally inside a stack view. We can also decide how to distribute them and how much space to leave between each view and the next.

Let’s see this in action. We will start from the UI of the CreateAccountViewController. Looking at our designs, we see that this UI is made of three text fields arranged vertically.

So we can place these text fields inside a stack view with a vertical axis. We then pin the stack view to the edges of the view controller. You can see how to do that here.

In the right panel you can see the settings of the stack view. In the left panel you can see the arrangement of the views inside of the view controller hierarchy. You can already see how this view controller will behave on different devices by changing the screen size in the bar at the bottom of the storyboard. You can see how to do that here.

Let’s have a look at a more complex UI. The CreateTransactionViewController has many more elements, arranged in different ways.

At the top there are two text fields placed vertically. Then there is a grid of 6 colored buttons to choose the category of the transaction. Each one of these has a label below showing the name of the category. We can achieve this layout with stack views too, but we have to use more than one and nest them inside each other. This is the layout.

Let’s analyze it from top down:

  • The top stack view in the hierarchy contains three stack views on a vertical axis. One for the text fields and one for each row of the button grid. This stack view is pinned to the edges of the view controller’s view.
  • Each of the stack views for the rows has an horizontal axis and contains three stack views, one for each button.
  • Each of stack views for the buttons has a vertical axis and contains the button and the label under it. Each button has layout constraints to set their width and height to make them square.
  • If you take a closer look, the button itself is not directly placed in the stack view, but inside of another view that is not visible in the UI. We will use this view to draw the blue circle around the selected category button.

Circles and rounded corners

As I explained above, all views are rectangular and so are our colored buttons. But in the design they are circular, not rectangular. I will show you now a quick trick to make circles or rectangles with rounded corners directly in Interface Builder

Interface Builder has a panel that few developers know, called “User Defined Runtime Attributes”, situated in the Identity inspector. This panel allows us to set the properties of a view that we cannot set in the Identity inspector. The Identity inspector does not expose all the properties of a view, especially the ones of custom views.

In iOS you can make circular views or views with rounded corners in the same way. All you have to do is alter the corner radius of the layer associated to the view. I am not going to explain you here what a layer is, because it’s a complicated thing related to the Quartz framework. All you need to know is how to do this. You can do so in code, or much faster, in Interface Builder, from the User Defined Runtime Attributes panel.

To make our buttons perfect circles, all we have to do is make them square and set the corner radius to half of the side of the square. So in our case, our buttons are 70×70, and the corner radius is 35.

Each button is inside of a view, which will be used to create the blue selection circle around the button. So we do the same for this view, to have a circle. Here view itself is bigger since it needs to go around the button. We can also set the size of the border of the circle in the same panel:

This panel would allow us to also set colors, but this does not work with layers. This is because they need a different type of color (a CGColor instead of a UIColor). We can do this only in code later.

Laying out cells and headers in table views

Now that you have seen how to arrange stack views inside stack views, the UIs for the other view controllers are quite straightforward. They all follow the same system. Let’s start from the AccountsViewController. This is how it should look:

Since the list of accounts will be shown by a table view, we only need to lay out one cell prototype that will be used for all the accounts in the list:

Here a colored view contains a vertical stack view for the three lines in the UI. The first line is an horizontal stack view with two labels, while the other two lines are simple labels.

Let’s see the TransactionsViewController.

Here we have two parts in the UI. The first is the summary for the account, with the total and the account number. The second is the list of transactions.

The summary appears only once at the top, so we can make it ther header view for our table view instead of a cell. Simply drag a new view into the table view to create a header view.

Again, all UI here is implemented with stack views

  • the header view contains a single vertical stack view with two labels
  • the table view cell has a horizontal stack view for the left and right sides of the cell. The right side is just a label, while the left side is a stack view with a circle view for the category and another vertical stack view for the description and the date.

Common reusable UI code

Before we go on and create the code for our UIs, I want to spend a moment to create some code that we will make our life easier later. When working with UIs there are little pieces of code that are likely to repeat around the app. This is because many things around our UI will look the same. This is code that does not belong to any specific view or view controller since it is generic and likely to be used in many places.

Colors are a good example here. The UI of our app has different colors. These are not random and belong to a specific palette. We use this palette for our transaction categories, but also for the different accounts in our list. We can set some of these colors already in Interface Builder. For example, the buttons in the CreateTransactionViewController already have the right colors set in the storyboard. But in other places we will have to set the color in code.

We can gather this palette in an extension for UIColor.

I didn’t write this code myself. There are different ways to generate this kind of code. I use Zeplin, which helps sharing Sketch designs within teams. One of its many features is to generate such color extension, with already named colors (I would have never came up with these color names myself).

The only thing I did add the last method in the extension that transforms an index into a color. With this method we can give an account a color based on its position in the list.

Another thing we need is to associate the categories to their colors and their icon images. For this I created a little extension for TransactionCategory.

Another common thing that happens in a UI is formatting data to be displayed on the screen. In our code, money is represented by Float numbers. In the UI though these amounts are shown in dollars, with a currency symbol and separators for thousands and decimals. The account numbers are also formatted to be more readable, with a space every four digits. The date of our transactions has a specific format too.

Formatting is something that does not belong to data, so it should not make its way to the model. Data in the model is independent from its representation, that can change in different parts of an app. Remember that visual representation is a duty for the view layer and not for the model layer.

So I created a couple more extensions to format our data to display it in the correct way in our UI:

Encapsulating UI code inside custom views

Now that we have the whole UI laid out, we have to connect it to our code, so that we can populate it with real data.

Views in a storyboard are connected to the code of our view controllers through outlets. So we can start connecting the text fields in our CreateAccountViewController.

This works well, and it’s a standard way of connecting UIs to code that I often use. But here I want to go a step further and show you a better approach for more complex UIs. Let’s take a more complex example.

Our CreateTransactionViewController has a more complex interface which has:

  • text fields for the amount and the description of a transaction
  • buttons for each transaction categories
  • a blue circle around the selected category

We can connect all these to our view controller through outlets and write code in the view controller. This would not be wrong. But let’s think a bit more what code we need to write:

  • we need code to put the circle around the selected category
  • we need code to translate the information the user inputs in the UI into a Transaction struct, which means we have to:
  • translate the content of the text fields to the proper type. A text field gives us back a string, but we need a float for the amount
  • translate the selected category from a selected element in our UI to an actual TransactionCategory value

This is not a lot of code, but it’s still code that would end inside the view controller. In the end it is also not something the view controller should care about. We can instead encapsulate all this code into a single specialized view to keep our view controller leaner.

This view takes care of all the outlets to the UI elements and the action connected to the category buttons. I won’t go into the details of this code because it’s pretty straightforward. What is important here is that the view controller will not have to deal with the outlets or with transforming the data. It can access all it needs through the properties of this view.

Custom table view cells and view models

Another common place where it’s good to create custom views is in the case of table view cells. These can deal with their own outlets and actions in the same way we did above. In the previous case though we were reading data from the custom view. In this case we will need to go in the opposite direction and pass data to the views.

Remember that a view should not know anything about the model of an app. All it need is data represented by basic types (String, Bool, etc). When creating our cells we could expose properties for each piece of data we need to pass to these views. But I generally use a different approach.

This approach takes inspiration from the MVVM pattern. I am not a proponent of this pattern which I don’t use in its entirety. The MVVM pattern (or Model-View-ViewModel) is an extension of MVC which adds a new layer, called the view model. The responsibility of the view model is to transform the data of the model into a format to be used by views. In this way the view gets served only the data it needs and the translation code does not end inside view controllers.

This is the only part I like about the MVVM pattern. Although the description of this pattern seems quite simple, in real MVVM the view models are classes that do many things. Many developers give them a lot of responsibilities, like networking (but there proponents of MVVM don’t agree with this). Moreover MVVM is often used with FRP (functional reactive programming) and a framework called RxSwift. I never use those either because in my opinion it creates more problems than they solve.

But I do like the idea of separating code that converts data for the views into view models. In my case though, these are structs and not classes. So they are simple value types that don’t have any side effect and don’t contain any other logic.

Let’s have a look at the table view cell for an account as an example:

Here the cell has a small struct called Model that exposes the data needed by AccountCell in the formats it needs (strings and a UIColor). This data is created from an Account and an index that the struct takes in its initializer. These are then transformed into the final values using the extensions we created above. The cell itself then takes a Model value as uses its data as it is, without operating any conversion at all.

We do the same for the cell for the cell for a transaction

Summary

In the lessons of this mini course we are moving fast and glossing over details. There is no time or space here to go over them, since what I want to give you here is an overview and understanding of how professional apps are structured. I will share with you more free material on the various aspects of iOS development after this mini course ends.

What is important to stress here is that:

  • views are positioned on the screen though Auto Layout constraints
  • stack views allow us to create grids without worrying about constraints
  • there is a lot of UI code that is needed all around the app but does not belong to any specific view. This code can be encapsulated into specific extensions.
  • we connect views to cose through outlets and actions. These can be placed in a view controller, but sometimes it’s better to encapsulate them into specialized custom views
  • we can draw ideas from the MVVM pattern and encapsulate code to translate model types into data for views inside view models

If you want to go more deeply into the all the details, my “The Confident iOS Professional” Course does so. It took me two years to complete it and to make sure I included every important aspect of developing complete iOS apps. The course opens regularly only to subscribers of my list. So if you are interested stay in my list and you will be notified when it opens.

Here is a preview of the modules in the course related to what we saw in this lesson:

Module 4 – Connecting the User Interface to Your Code: The View Hierarchy, Outlets and the Target-Action Pattern

  • What role views play and how to assemble the user interface of an app using basic Auto Layout. This forms the basis of a dynamic user interface that adapts to all the different iOS devices
  • How views are structured in a hierarchy that makes them more manageable.
  • Outlets and actions, the core mechanism used to connect the user interface to the code you write in a view controller.

Bonus Master Module: Working Without Storyboards – Using Nib Files and Laying Out Interfaces in Code

  • What are the real reasons you should carefully consider when deciding to use nib files or code instead of storyboards. Many developers choose one option based only on their personal preferences, but this is often a mistake that brings high hidden costs and severe problems.
  • The fundamental differences between UIs laid out inside storyboards, nib files or code. Each solution has hidden pitfalls you need to properly consider, especially when you mix storyboards and nib files in the same app.
  • How to manage the app flow and dependency injection when you lose access to storyboard segues. Most developers follow common practices that are not thought out properly and lead to highly coupled code that is hard to maintain.