View Code for iOS —A control framework to eliminate interface builders
Every new developer who starts learning iOS always see Xcode one of the most beautiful frameworks due to the fact that the first thing you face when creating a new project is that wonderful file called main.Storyboard, which allows you to build all your interface and screen flow in a totally intuitive way. When I coded for Web and was used to program with HTML and React to build my screens I didn't believe on when I started with Swift…Imagine that, instead of building that screen with all those open-close tags manually and using declarative code, I finally could plan my screen through all those tools Xcode interface build could give me.
When I did iOS applications with a single storyboard holding all the screens and flows everything was perfect , until I started working with greater than five people teams and it became hard to split code within a single file to share with other people
The worst points in storyboard:
- Hard to work with version control: Every time we have a project with only one or a few storyboards, certainly the team members will modify the same files, this way generating a lot of conflicts
- Totally limited considering all the possible layouts and animations we can build in the front-end. If you think Xcode's interface builder, with all the storyboard and xibs allows you to build millions of different screen styles and layouts for different types of applications, you cannot even imagine what Swift can really do for you. For example, you cannot trim a text into different styles pieces.
- Makes your UI code incomprehensible to someone who just joined your team and needs help to understand its architecture. If it is already heard to study all the code, just think having to relate each UI element with its respective IBOutlets and each button to its respective events(IBActions)?
- It isn't simple to transfer data using UISegues. Let's think, ok, it is fine that for changing screens all you must have is an identifier of where it is heading to, but the method "performSegue" does not talk about which informations must be passed to the next screen to be loaded and how will occur the transition. In this way you end having to instantiate an object of the next view controller to transfer every stuff.
What is View Code
Ok, let's stop talking about storyboards and begin with what we are supposed to talk. As you may know, View Code is an alternative for who doesn't like the interface builder and wants to really know deeply how to explore UIKit's potential(and other frameworks). The first thing to say is that, different from setting properties with user-friendly widgets in Xcode, you must know each class which make the UI work. You need to instantiate each button, text or field and set its properties by code.
But obviously, this work of building a new screen by View Code does not consist only on this…If you have already tried to create a new interface by code in Swift, certainly you had your project crashed by disobeying the components hierarchy. In other words, you may have tried to set constraints relating two views before building their hierarchy(a.k.a view.addSubview()). There is a specific order to guarantee everything works in your UIView/UIViewController and we will talk about it above.
- Create your View Controller
- List each UI property your view must have
- Build the hierarchy of its components
- Set up their constraints to guarantee a good layout in any size of screen
- Configure the components(define fonts, colors and events)
Let's create the following project as an example:
My Login App —Initial setup
The app we are aiming to do is extremely simple and consists only in a single login screen which allows the user to insert his login and password, and when he clicks the "Enter" button, a message is printed. It has very few UI components, but our goal is only illustrate the framework behind the steps we will follow through the ViewCode. First things first, create a Xcode project for iOS using the Single View Application template. Name it as ViewCodeExample
As we know, when we create a Xcode project, it automatically sets a storyboard for us together with a ViewController file, a Launch storyboard and an info.plist file with the default configuration for the project. We are not going to work with this stuff, as we are not supposed to use interface builders, so delete all these files. Yes, exactly how I said, delete those three files and run the project again.
When trying to run, the project just crashes, right? This happens because the project is configured to instantiate a view controller from a storyboard called "main", and as we deleted this file, it throws an exception which will not allow the project to work correctly. This is easy to solve: first go to the project file(in blue at the top).
Go to "Deployment info" section at the field "Main Interface", which is currently set as "Main", make this field empty. This way we are saying that the interface doesn't come from a main storyboard anymore.
Great, but before coding, there is some more changes we must do in our info.plist file. Go to the info.plist and follow the path Application Scene Manifest > Scene Configuration > Application Session Role > Item 0(Default Configuration) > Storyboard Name. Since we are not working with storyboards, just delete this field.
Good, now our project is configured to not depend on any storyboard to present the first screen. Now what we are gonna do is initialise the first view controller of our project by code. Let's create a new class called LoginViewController. Create the new class with Cocoa Touch Class template and make it inherit from UIViewController, make it without a Xib. Cll it LoginViewController, and after that, delete everything leaving only the viewDidLoad method.
Now we have an empty view controller, which represents only a white root view. Wait, how are we gonna make our project consider this view controller as first screen? If you are on Xcode 11, go the SceneDelegate.swift file.
Inside this file, delete everything that is not the first "scene" method and leave its body exactly this way:
Basically this class instantiate our main window through a UIScene. A UIWindow is the root container that will give visibility to the views. When instantiating our view controller, we are declaring that this window has it as the root view controller embedded in a UINavigationController. Later. we make it visible. The method sets the LoginViewController as the first screen we shall see when running the app.
Now if you run the project we will see an empty screen, since we didn't insert anything in our view controller yet. We will start doing this by now.
The ViewCode Protocol
Now its's time to insert all of our components into our login screen defining how it will behave. As I mentioned before, most people don't take care when building the view layout and follow the steps totally out of order instead of following a specific framework. What I bring here is an architectural solution for ViewCode which will avoid most errors of this type when building a view by code.
Now you must create a new swift file named ViewCodeConfiguration.swift. It will hold our protocol that will define the order of things that shall happen in our UI.
The protocol is described bellow:
The protocol above defines three methods to be implemented:
- buildHierarchy: here you must build all the hierarchy of the subviews that will shall be inserted in this screen or other kind of view. In other words, it only makes each view or subview to add another view inside.
2. setupConstraints: this method will take all the subviews in the hierarchy defined previously and will describe how will appear the layout between then in distinct sizes. Here you may set all the constraints of our screen.
3. configureViews: as you see, it is implemented as an empty method at the protocol's extension, making it optional. In this method, you must apply all the extra configuration and properties to the subviews, defining text, accessibility, internal alignment, colors and targets(if it is a button).
4. applyViewCode: At the end, this method calls all the others making them execute in order, since for example it does not make sense to add any constraints before building the hierarchy, throwing an exception. This method is what resumes all the process.
Customizing with ViewCode
Now that we defined a protocol to be implemented by the controller, what we will do is apply it. But before anything, there is something important we didn't do: we must define which are the UI components that will belong to our view controller.
Observe how it may be:
With this layout we can have a better notion of which components we must use. We are going to implement a UIImageView with the Swift logo, two UILabels and two UITextFields for the login and password. Bellow, we present a button.
The properties are declared as:
Observe that we defined all of them as lazy properties and we have already instantiated them at the beginning. It will avoid some pointer issues at the time of creating our hierarchy. We defined the dimensions as zero because all of them will be calculated when setting the constraints.
Now that we know which are the components that will take part at our hierarchy, let's define how it will be organised. Create after the LoginViewController class an extension that implements the ViewCodeConfiguration protocol:
Before we forget, call the applyViewCode method in viewDidLoad body. It builds the screen just when the controller loads at memory. Also give it a title:
Now we must begin building the screen starting by its hierarchy. As we can observe, all the subviews are inserted into a single root view and we are not gonna use any intermediary containers. So, the root view shall be every other component's parent:
Now that we defined all the components belong to the root view of our controller, let' s describe how their layout will behave in different devices. We will make it in the method setupConstraints:
We won' t enter in SnapKit details since it is not this article's goal, but you may notice that all the subviews position and dimension are defined by their constraints in each screen size. For instance, our imageView is placed 140 pixels bellow the top of the screen and is centered in the X axis with height and width 150 pixels.
We now have the subviews hierarchy and layout defined, but only their configuration is missing. Let's add some properties to our subviews in order to guarantee their good look and features.
Take a look above and see we inserted all the component's properties, such as content mode, texts, color, border properties and targets.
Run it again, and see it now appears like we wanted at the beginning. All those methods that build the screen are executed in the correct order to initialise it.
In this article we presented a good solution for building a view with ViewCode in iOS following the required order to avoid issues. We also presented a protocol which guarantees everything is done with the correct steps, splitting the operations into hierarchy, layout and properties.
This protocol can be implemented by the controller, which holds a root UIView, or by another UIView, since the same also takes care of an internal hierarchy
Following this framework you have better control of your screen building process and learn more about Apple's UI tools.
I hope you have learned something new, see ya! :)