The Ultimate Course to Making Professional iOS Apps – Lesson 3

The Ultimate Course to Making Professional iOS Apps

Lesson 3: defining the flow of an iOS app through view controllers, storyboards and segues

In the previous lesson we have seen how the model layer encapsulates data, business logic and transformations. We also saw how model controllers manage the handling of data for tasks like keeping the state, storing the data and others.

In this lesson we will move up one layer in the MVC pattern and deal with view controllers and the flow of an app.

The role of view controllers in iOS apps

We have seen when we talked about the MVC pattern that view controllers are mainly concerned with views. And indeed view controllers in iOS have a role that is more skewed towards the user interface and interpreting user interaction.

View controllers in iOS have these responsibilities:

  • they usually represent only one screen of an app. As such they manage a view on which the user interface for a single screen is laid out
  • they pass data from the the model and model controllers to the views
  • they react to user interaction, changing the app state or triggering events in model controllers
  • they present other view controllers
  • they communicate with each other to propagate data and state across the whole app

As you see they have many responsibilities that are very distinct from the ones we have seen for the lowest layers of the MVC pattern.

As I said, view controllers mainly represent one screen each and present other view controllers. Modal presentation happens when a view controller comes on the screen from the bottom.

Although very important, this is not enough. Real apps usually have a more complex flow that needs different types of interaction. For this reason in iOS there is a special class of view controllers. These special view controllers are called container view controllers. They behave differently from normal view controllers. In fact, they don’t deal with the responsibilities listed above. Instead:

  • they manage other view controllers. As such, they do not represent a screen, nor deal with passing data from the model to the views
  • they manage the transitions between the view controllers they manage and their lifecycle. Containers instantiate view controllers and bring them on screen in different ways.

A very common type of container in iOS is the navigation controller. A navigation controller manages a stack of view controller in a drill down model. Each new view controller managed by a navigation controller comes on the screen with a right to left animation. It usually shows more details about a selection made in the previous view controller. Navigation controllers also provide the navigation bar at the top of the screen, which includes a title and buttons, like the back button to go back to the previous view controller.

Navigation controllers are not the only containers in iOS. There are other ones and it’s even possible to create your own containers. What is important here is that:

  • a view controller manages one screen
  • a container manages different view controllers

Defining the flow of an app through storyboards and segues

We have clarified what the role of view controllers is and how containers make different interactions between view controllers possible . What we are missing is a way to define relationships between all these.

It is possible, of course, to do all this in code. But this is a quite cumbersome and scattered approach. Apple introduced storyboards and segues to enable developers to map the flow of an app in a single place, using a visual editor and consistent paradigms.

  • A storyboard is a visual map of all the view controllers in an app, including the containers. In a storyboard it is possible to create all the user interfaces for every view controller and define relationships between them. In general an app has only one main storyboard, but it’s possible to use more when an app grows.
  • A segue is a connection between view controllers in a storyboard. There are different types of segues. It is important to know the difference between these:
    • relationship segues define the relationship between containers and the contained view controllers
    • action segues define a transition between view controllers. This can be either modal presentation or performed by a container
    • unwind segues revert a transition and go back to the previous view controller, independently of how the forward transition happened
    • embed segues are used to create simple custom containers. I won’t talk about these because they are not used often, but they can still be quite useful

Now that we laid the foundations on which our app’s flow will be defined, let’s put it into practice.

Laying out the initial flow

We are going now to lay out the flow of our entire app in the main storyboard. This is something that usually would be done incrementally, but there is actually nothing that forbids us from doing it all at once. We can create the whole flow of the app even before we lay out its user interface. In this way you can have a good idea of how the app is structured before we get deep into creating the UI.

If you don’t know how to use Interface Builder inside Xcode yet, you can find a quick guide here.

Our app, like many classical iOS apps, has a navigation bar at the top where buttons go. Moreover when selecting an account we drill down into the details of all the transaction. This means that we need a very common iOS view controller container: the navigation controller.

We will start placing a navigation controller in the storyboard of our app. When you drag a new navigation controller in a storyboard, this comes already with a table view controller attached. If we look at the design of our app, the first screen has a list of accounts. Lists in iOS are handled through table views, so this table view controller is what we need. This is what we want to get:

Still, I usually replace table view controllers with normal view controllers with a table view. This is because the former create problems later, with more complex UIs. To do so, delete the table view controller in the storyboard and add new, normal view controller. You will also have to reconnect the new view controller to the navigation controller.

But for our app it is the same, because our first screen does not have anything else in it other than the table view. When you replace this view controller with a new one, you will have to connect the navigation controller to it, through a relationship segue. Just control drag from the navigation controller to the new view controller and select “root view controller” in the pop up menu.

Our first screen has also an add button in its navigation bar. We can set that already, dragging a “bar button item” into the navigation bar and setting its type to “Add” in the “System Item” property in the inspector panel. If you replaced the view controller you might need to drag a navigation item in the navigation bar first.

We can also set the title of the navigation item to “Accounts”. The table view needs to have one prototype cell only, since it will contain only accounts. As I said, we don’t need to lay out the cell prototype yet. We can leave it blank for now. The result is in the picture above.

Navigating to a new view controller

When the user select an account, we want to navigate to a new view controller that shows the details of the account and its transactions. We can add another view controller for this, like the one we added for the accounts. We then connect the two view controllers through a segue. This segue needs to be triggered when an account is selected, which means it needs to start from the table view cell.  To create such segue you have to control drag from the table view cell prototype and not from the view controller itself. Then select the “Show” action segue from the pop up menu. What a show segue does is trigger a generic transition based on its source. If the origin view controller is contained in a navigation controller, the latter will show the next view controller bringing it in from the right edge of the screen.

This new view controller also needs a table view, a tittle and an add button in the navigation bar. This is the result:

We can run the app, but we cannot test this connection yet, because the table view has no data. We will have to wait until we will fill the table view.

Presenting view controllers modally

We now need the screens where the user can add new accounts or new transactions. As we discussed in the requirements, these screens need to be presented modally, with an animation from the bottom. Let’s start with the view controller to create a new account.

There is an important thing to keep in mind when presenting view controllers modally. Since these come over other view controllers, it means they are not contained in any of the existing containers we already have. They are on their own.

We want this view controller to have a navigation bar too, where we will place the cancel and save buttons. But since this view controller is presented modally, it won’t be contained in our existing navigation controller. This means it has no navigation bar. You can try to add a navigation bar to this controller alone, but that creates weird problems in the UI. Navigation bars work only with navigation controllers. The solution is to add a new navigation controller that will contain our new account view controller. We do this just to have a navigation bar, even if we are not navigating to any other screen from here. Then we present the navigation controller modally.

To make this connection, control drag from the add button in the first view controller to the navigation controller and select the “Show” action segue again. The cancel and save buttons are also standard system buttons, like the add buttons we added above.

But wait a minute. Why does this trigger modal presentation instead of drill down navigation like it did before? Both segues are action segues and seem to originate from the same view controller. As I said, this segue depends on the origin to decide how to move to the next view controller. If the origin is inside a navigation controller, the show segue triggers drill down navigation.

In this case though the origin is the button in the navigation bar. The navigation bar belongs to the navigation controller, not to the contained view controller. This means that the origin is the navigation controller itself, which is not contained in anything. So the presentation will be modal.

We can now complete the flow of our app, doing the same for the view controller to add new transactions. This view controller also needs a navigation controller and cancel and save buttons in the navigation bar. This is the final result:

Classes for custom view controllers

As I mentioned in the lesson on the requirements for our app, since we the app has four screens, we should have at least four view controllers. Now you see why. The total count is seven, because we have to include three containers (navigation controllers).

But the custom view controllers, the ones that actually represent the screens of our app are indeed four. These are the view controllers where we will write our code. Containers, on the other hand, already have all the functionality we need.

To write the code for our view controllers, we have to create custom classes for each one of them. All view controllers in iOS need to be subclasses of UIViewController, since this superclass already implements a lot of the behavior a view controller needs. Thanks to this, we only need to override the right methods and provide our custom code.

After creating the classes, we have to set the identity of our custom view controllers in the storyboard, to make sure that the instances of these view controllers will have the right type.

Going back to a previous view controller

The flow of an app does not proceed only in a forward fashion. Mosto of the time the user needs the ability to go back to previous screens.

Sometimes this is handled by containers. A navigation controller provides a back button in the upper left corner to allow the user to go back to a previous screen. So going back from the transactions screen to the accounts screen is already handled and we don’t need to do anything.

But we also present some view controllers modally. In this case they are not contained in a container, so we have to explicitly define how to go back. Remember that our custom view controllers are contained in navigation controllers only to have a navigation bar at the top. What we present modally are these navigation controllers, which are not contained into any other container.

Back transitions in iOS can be triggered in different ways. The most consistent one though is using unwind segues. In this way all the navigation in your app is defined in the storyboard, which is the central place for all the app’s flow.

Unlike forward segues though, unwind segues need some code to be written. Unwind segues do not only allow to go back to a previous screen, but also allow to jump back many steps in the navigation. For this reason, we need to write code to identify which view controllers we want to go back to.

This code takes the form of actions in the view controller we want to go back to. From the CreateAccountViewController we want to go back to the AccountsViewController and from the CreateTransactionViewController we want to go back to the TransactionsViewController. So it’s in the latter ones that we have to add our actions

Now that we have the actions, we can connect the cancel and save buttons in our view controllers to them in the storyboard, control dragging from the buttons to the “Exit” icon at the top of view controllers in the storyboard.

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:

  • View controllers in general represent the screens of an app. They can also present other view controllers.
  • Special view controllers called containers manage other view controllers to allow complex interactions. The navigation controller is a very common container used in iOS apps.
  • You can lay out the whole flow of our view controllers inside of a storyboard. Here you connect them through segues, which define the transitions from one view controller to the next.

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 3 – The Skeleton of iOS Apps: View Controllers and their Lifecycle

  • How view controllers create the skeleton of well written iOS apps and what their central role is. Getting this wrong will make your app misbehave and you won’t understand why
  • The crucial sequence at the beginning of the lifecycle of a view controller, as it gets created, initialized and brought on the screen
  • Common misconceptions developers have about view controllers who lead to architectural mistakes that make them hard to change, reuse and test

Module 5 – How to Structure the Flow of any App: View Controller Presentation and Container View Controllers

  • How the flow of different screens is actually created in iOS. This is what enables you to use the standard transitions of iOS without having to re-invent them yourself
  • How view controllers present other view controllers on the screen. This is the common interaction pattern to use when you need to interrupt the app flow to prompt for an important action from the user
  • How to create apps with many screens and complex flow using container view controllers. This is what makes the difference between a sample toy app and a real world app
  • The available container view controllers of iOS which allow you to structure the flow of an app with many screens and different interactions

Bonus Module – Moving backwards: Unwind Segues and Passing Data to Previous View Controllers

  • Why unwind segues should not miss from your toolbox are and why you should use them in a well architected app, even if you are already using other techniques in your apps
  • The precise details behind unwind and how they are more complex than normal segues. This is why many developers fail to understand them and keep using other sub optimal solutions
  • How you can validate data before a segue happens and even prevent it from happening. This works for any segue but is an especially important step when using unwind segues to validate user input

Master Module 4: Advanced Architecture for Complex iOS Apps – Injecting Dependencies and Managing Flow with Custom Storyboards and Segues

  • The key points of the MVC pattern that create serious architectural problems in complex apps. If you don’t know how to identify these points, you will end up with a garbled code base that is hard to disentangle.
  • How to take the important duty of dependency injection out of view controllers, where it does not belong. Not taking care of this leads to a lot of unused and confusing dependencies in all your view controllers.
  • How conditional app flow in view controllers creates unnecessary and problematic coupling. When you do this you find yourself with complex code to do something that could be done in an easier and cleaner way elsewhere.

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.