View Code — Tips for a better use

Raíssa Nucci
Movile Tech
Published in
13 min readMay 29, 2020

[Thanks to my co-author Rodrigo Máximo]

Introduction

This is the second article of a series in which we discuss two different ways of designing iOS views and screens: Interface Builder’s Storyboards and Xibs, and plain old view coding. If you haven’t read the first one, here’s the link.

By no means we are forgetting about SwiftUI, the new Swift-only Apple framework, which gives us the experience of view coding and interface builder both at the same time, and has advantages and disadvantages of it’s own. But getting into those is not the purpose of this series.

In this article we are going to present some examples and personal experiences on how to structure and organize your code when building your View Code layouts so you don’t get overwhelmed by the possibilities, and make sure you are truly taking advantage of the benefits it can bring to your project.

Where goes the code?

If you’ve chosen to use view code in your project you most likely agree with some of its benefits over Interface Builder approaches and would certainly want to take advantage of those.

Some of these benefits are inherent of the code itself, such as meaningful and semantic merges and lighter project and files. But not everything comes on a silver platter. If we want to achieve the best result we possibly can on a project, it is not enough to choose one approach over the other. We have to put on some brain power.

If you start building your view code with the exact same mindset you had when building views on storyboards, you will possibly end up with a massive ViewController where all the views setups are done, as are constraints setups and other view manipulations triggered by user interaction. Can you imagine that mess? You would barely feel the benefit of merging and reviewing code instead of XMLs in this scenario, so chaotic it could turn out to be.

Extracting code from ViewControllers

We should think of our ViewControllers as solely a proxy between views, user interaction and the business logic. As we should not put all our business logic on ViewControllers, and therefore avoid Massive ViewControllers, we also shouldn’t throw all our views setup, rendering and manipulation there, on the intent of avoiding the exact same problem. That said, let’s move our view related code to another file.

We want to build a view, so we will have a class inheriting from UIView and there goes all view and subviews setups and methods for manipulation. But wait? How to bind that view to our ViewController?

Obviously this binding could be done by simply adding a new view property to the viewController and constraining it to the view controller’s root view. However, that looks kind of odd doesn’t it? We would still need to have view setup on the view controller to add that new view as subview and constrain it. This is where loadView() UIViewController’s method comes to the rescue.

loadView() is a default UIViewController method which is responsible for creating its root view. There is nothing more natural than using this method to set our custom view as the view controller’s root view, by overriding it.

Now, the view controller’s view property is our view and it will be loaded and displayed as the root ViewController’s view!

However, as the view property is a UIView type, we don’t have access the specifics of our custom view. Luckily, it can be easily solved by simply adding a property in this ViewController of a CustomViewProtocol type. This property will be a reference for our custom view, in order to allow us to have access to those specifics.

That’s it! We were able to remove all our view code from our view controller, making it simply a proxy between user interactions and view logic, since we made sure our customView is now the ViewController view. Also, through that protocol, we gave ViewController access to all view’s specifics, for example methods to render properties of that view, setup delegates, render a loading state, etc. Besides, through this protocol implementation, we’re able to test the ViewController easily, for example mocking the custom view implementation.

The custom View

Well, in that previous section we extracted the ViewController’s root view implementation from the ViewController itself. Now, the only thing we need to do is implementing this custom view.

Let’s take a look at the following iPhone screen and write the exact code that will reproduce this screen. We’re going to use the Cartography library to help us (it makes the auto-layout code a bit less verbose and makes the learning curve a little less steep), but we could also implement this through Apple’s native auto-layout APIs.

The goal here is to present some ideas of how to structure and organize the code, and also some style guides we believe to bring maintainability and readability to the code, at the same time as they guarantee its testability.

Declaration of subviews follow an hierarchical order

The first recommendation we have is to declare all the subviews in a hierarchical order. This came to be a very important trait of our projects and therefore something we’ve defined as a rule in our code style guide.

But why is this so important to us? Well, think of when you need to maintain some View Code you were not the responsible developer, and you know nothing about it. Since we’re not using storyboard, it can be difficult at first to visualize how this view actually looks like, how all the subviews are disposed in relation to themselves and also to their superview. So, if the declaration of all the subviews properties of this view follows this rule, we would make the understanding and visualization easier and faster.

We’re using closures to declare the subviews variables. This may also be a good practice, since we centralize all the features of a subview in its closure, instead of spreading throughout the code. Besides, the use of closures here is useful to allow declaring those variables as constants, what guarantees us properties immutability.

Configuring constraints in hierarchical order

Now the views are all declared, we have to configure them. By configuration we mean adding the subviews to a parent view and also setting their constraints.

Something important to take note is that constraints can only be added for views that are in the same hierarchical tree. This means before adding constraints to a view A relating it to another view B, both have to be added to a parent view and both may have a common ancestral view. Otherwise your application is going to crash.

Firstly, as we should do in any written code, we follow the Single Responsibility Principle defined by Uncle Bob’s in SOLID principles, by declaring methods that configure a unique subview, nothing else. Also it means we declare methods following the “stepdown” rule, which, as described in his book Clean Code, is basically declaring methods in a top-down narrative way. This means every method is followed by those at the next level of abstraction, so that the program can be read, descending one level of abstraction at a time, as reading down the list of methods. In practice, this means that if a method A calls methods B, C and D in this sequence, and B calls method E and D call methods G and H, we declare and implement them in such order:

  • Func A
  • Func B
  • Func E
  • Func C
  • Func D
  • Func G
  • Func H

Secondly, we declare each method responsible for configuring each subview in the same order as these subviews are added to their superviews, and also following the UI axis according to the views position in the screen. This again helps in maintenance, because we will know more easily the visual position of these views just by checking the configure or declaration methods order.

Thirdly, if you notice, each method is declared according to a script. Each one of them has the addition of this subview to its parent (1), followed by the definition of its constraints (2) and then the call of all of its subviews configuration methods (3).

Finally, we always declare all the constraints of the view being configured in relation to other views in its setup method (just for those already added to the view hierarchy), another convention we’ve adopted as style guide and which really helps in terms of reading and understanding a view code implementation. This avoids a component constraints being spread across the code.

Overview:

  1. SOLID and step-down rule
  2. Declaration of subviews in the order they are visually in the screen
  3. Script to each Subview setup method
  4. All possible constraints of a subview defined in its setup method

View Code using Cartography

Now, let’s take a look in different ways of declaring constraints. In the previous code examples, we’ve already used the Cartography framework to declare them. It makes really simple to declare constraints.

The only thing to do is calling the constrain() method, passing all the views that are going to be referenced to create and add the constraints. Then, in the closure parameter of this method, we can reference each one of those views and declare the constraints we want to as comparisons.

View Code using Apple framework

Now, the same code above written using the native Apple framework, the NSLayoutConstraint. This is much close to previous one, and to be honest we don’t have a preference. Maybe with Cartography the written is a little bit faster, but on the other hand, not using a framework for this may be better depending on the application you are developing.

Something important to remember though, before adding constraints to a view, is making sure its translatesAutoresizingMaskIntoConstraints property is set to false, otherwise your constraints won’t have any effect once your view is supposing it has a frame based layout instead of auto layout. Most 3rd party auto layout libraries do that for you under the hood, so you don’t have to worry about that. But you must remember that configuration if building your views using only Apple’s API.

The same method to setup the horizontalStack view with Cartography is declared below using Apple’s API.

Separate Views in Different Structures and Files

As it might have become clear, View Coding follows the exact same principles as any coding and therefore is subject to the same best practices. As we do with other parts of our architecture, the code responsible for creating and handling our views should also suffer the influence of Object/Protocol Oriented programming, componentization, reutilization and separation of concerns.

That said, when writing your views, keep in mind they are components of your architecture just as any other, and apply to them the same principles as you would to other components. Break your view into smaller view files and use composition to build more complex ones. That will help a lot with visual components that are repeatedly used throughout the app. You can simply declare a new variable of that view type and not worry again about most of its configurations and, most importantly, its behaviour.

Componentizing our views not only helps with reusability but also with keeping your files small and concise, which helps with understanding. As with any other code, inheritance is also a valid technique when it comes to view coding, and, as in any other code, compositions are also preferred over inheritance in this context.

Create structures for Colors, Margins and Fonts

Creating auxiliary structs and components to your view code is the key to solving one of the problems faced when using Interface Builder (specially in applications that count with many developers): colours and margins standardization.

By simply creating extensions of UIColor and UIFont or even a Style struct containing our definitions, we can centralize all colors, fonts and margins specifications and use these auxiliaries in our view classes.

With those structures and extensions the view code will be more readable and also you’re going to centralize those properties, making eventual changes much easier.

Data Flow between screens

As mentioned on the previous article, using Storyboard can be quite a pain when it comes to controlling the data flow between screens, which is an extremely important piece of an app. Designing views using view code may bring great benefits regarding this.

The problem with Storyboards is that you don’t have control over the ViewController’s initialization flow, since the storyboard returns them already initialized. That way, it is hard to pass arguments to the ViewController initializer when using Storyboards.

Without control over the init flow, transferring data will probably have to be defined as not private optional vars on the destination ViewController, which can cause some side effects. Firstly because those properties will be exposed to external changes. Secondly because we’re creating an optional “environment” to the next scene, that doesn’t make sense in most cases, since that scene shouldn’t even exist when those data don’t exist. So, every use of optional variables is going to be followed by if let’s or guard’s.

Besides that, if using segues, we must rely on string identifiers. If those change on the Storyboard and not on the prepareForSegue method, destination ViewController won’t be found and properties won’t be injected in the next scene, leading to wrong behaviour due to nil values or even crashes .

On the other hand, when initializing ViewControllers by code, we have control over everything! We can have custom inits, where we inject all the information needed for the correct behaviour of that screen. Those properties can be let and private, which simplifies all the logic inside that ViewController and guarantees we won’t have unexpected side effects.

More over, injecting that ViewController dependencies through its initializer is a great step towards good code testability, once we can test that ViewController behaviour and relationship with its dependencies through the injection of mocks.

With that in mind, let’s have a look into the following Next Scene example, a view code implementation of our next scene, which will receive data from the previous viewController.

Supposing our example ViewController, the one which will transfer data to the next one and make it after its button is touched, the transfer implementation through custom init would be something like this:

Just notice how simple and clear this is, and also that the transferred property is private and not optional in the next ViewController. 🙌

Let’s Test It

Most of the time when we hear about testing iOS application, it’s not common to include tests for views and view controllers on the scope, or if it does, it is through UI Tests or Snapshot tests.

It can be very harmful not to test our view controllers, once all the apps fundamental behaviour starts with them — by user interaction. Furthermore, with view coding, MOST of your code base will probably be view and view controller code (we’re not mentioning all iOS architectures, such as MVVM, VIPER, Clean arch, etc, since it’s not the article purpose).

Let’s then talk about something not usual, unit tests for those components. We’re not going deep in terms of tests theory in this article, but one really important and basic thing when talking about this, is abstraction through dependency injection.

This is really helpful when we desire to test some code, because this allows us to create fake implementations (mocks and spies) and inject those in the component being tested.

For example, rewriting our ExampleViewController and adding a Presenter (supposing we’re using a MVP architecture), see how we could define the protocols and the dependency injection. The ViewController would have a reference to the Presenter, and the Presenter would have a weak delegate to notify the ViewController about the business logic.

This protocol is just an example, and we’d have a concrete implementation for this, for example a ExamplePresenterConcrete class. But since we’re just talking about tests, we won’t show this implementation once it’s not necessary.

This implementation clearly have some things we need to test. We need to test if in the viewWillAppear calling the ViewController asks to presenter to fetch the data and calls customView to present the loading visual state. Also we need to test if when the data is fetched we render it in the customView.

So we create the mocks implementations, and after that we test everything we need.

Something we do that is really interesting, and not only for testing views, but for all kind of unit test, is declaring the properties that will be tested and that need dependency injection as lazy var’s and the mock and spies properties as let. We can do that because as it’s said in this Apple documentation in “Flow of Test Execution” section, “For each test method, a new instance of the class is allocated and its instance setup method executed”.

This is helpful because it avoids optional properties, and also declaring the object under test and its dependencies for each test method.

Doing that, we’ve accomplished our goal of creating unit tests for the ViewController.

Finally, something important to say is that without the injection in the init of object under test, to check that property behaviour or state, after some call in that object, we’d have to expose that property (turn it not private).

Also, without the injection through a protocol abstraction, we’d have to create a mock implementation that would have to inherit, another disadvantage since it’s usually better to compose than inherit.

Conclusion

View coding follows the exact same principles as any coding on any other part of your app architecture. It has its inherent advantages but you will only get the best of it if you apply coding best practices and principles.

Here we presented some styleguide rules and suggestions we believe can make your experience with view coding smother and most enjoyable, besides improve your view code maintainability.

We also want to emphasize that view coding can help with code testability and unit testing. Your views and ViewControllers will not only boost your test coverage but also save you from some nasty bugs that could be hidden under that much code.

--

--