Implementing iOS interfaces in viewcode

Brenno de Moura 🏳️‍🌈
5 min readAug 21, 2021

--

iOS apps that support UIKit need to be implemented using storyboard + xib to define the interface elements of a view controller. This implementation rule, defined in Apple’s documentation, is subject to many object development and integration issues.

Also, working on storyboarded apps requires knowledge of methods for navigating between screens and passing values between screens. Therefore, this approach is precarious and needs to be refactored into the view code implementation model.

UIKit is a framework for iOS that allows you to build an interface in two ways: using storyboards or implementing in Swift and obj-c. Because of this, it is possible to adopt the view code model in iOS apps and not use storyboard and xib to create interfaces.

In the iOS context, we have two graphical components that are part of the UIKit rules to maintain software integrity. The two components are the UIViewController and the UIView.

The view code process requires the programmer to know these two classes that will facilitate when defining the application rules. First, the UIView coding process will be discussed, and then the UIViewController coding process.

UIKit explores the imperactive programming. It is complete, mature, and allows the interface implementation by view code.

UIView

The UIView class is the basis for all UIKit graphical components. With it, it’s possible to implement buttons, texts, and complex components such as lists and collections. In addition, UIKit has an auto layout mechanism to define view position rules, such as height, margins, and width.

This variety of components and tools makes UIKit a complete and mature interface framework that allows the implementation of the interface via view code.

The first step to implement the interfaces is to define the programming format of the views. In this article, we’ll use a syntax closer to declarative programming, inspired by SwiftUI, and lazy var variable attributes.

UIKit is a framework that explores imperative programming, where the program follows a flow of instructions to get the desired result. Because of this, it’s necessary to make some adjustments to transform it into an almost declarative framework.

UIKit is a framework for iOS that contains two graphic components that belongs to its rules: the UIViewController and the UIView.

An important detail of UIKit is that all graphic classes like UIView and UIViewController inherit the NSObject class. In this way, an exclusive abstraction will be made in this base class.

As shown in Figure 1, the KeyPathSettable protocol is defined with two methods: (a) setting<Value>(_ keyPath: WritableKeyPath<Self, Value>, to value: Value) -> Self; and (b) setup(_ setup: (inout Self) -> Void) -> Self.

Due to a limitation of Swift, these methods are only defined in the protocol extension to allow use in classes that inherit NSObject. Method (a) should always be used to set layout properties in the declarative flow, such as text, background color, border, and others. Method (b) should be used to define more complex properties that are inaccessible through KeyPath, such as height and width constraints, or functions such as registering cells in the UITableView.

Figure 1 — KeyPathSettable protocol definition.

After defining and implementing the protocol, you need to create a second NSObject+KeyPathSettable.swift file to integrate them. As the methods were implemented via protocol extension, it isn’t necessary to implement any code in this file, just the NSObject extension conforming to the KeyPathSettable protocol. After that, you can access the UIView properties, NSLayoutConstraints through the setting(_:to:) and setup(_:) functions.

The second protocol is ViewCodable which will allow the configuration of subviews and auto layout constraints. This protocol has three methods: (a) buildHierarchy; (b) setupConstraints; and (c) applyAdditionalChanges.

In the extension of this protocol, we must add the setupView method that will call the three functions sequentially. With that, the implemented views must call the setupView method after their initialization, which will assemble the hierarchy and configure the auto layout. As shown in Figure 2, we have the definition of ViewCodable with the setupView method.

Figure 2 — ViewCodable protocol definition.

By integrating the two protocols, we can create a programming model for all application views, especially when considering the use of UIStackView. Figure 3 shows how this happens.

All view components that display some information on the screen or have some user interaction, usually text, and buttons, are specified separately using lazy var.

If the view has several interface groups, one part shows the user’s first and last name, and the other shows the address with a map, it can also be broken into independent variables with the lazy var.

Figure 3 — UseView exploring the ViewCodable and KeyPathSettable.

The final variable used to build the hierarchy is the contentView. We use it and the UIStackView to build the interface simplifying the implementation of ViewCodable methods and with only zero margin constraints.

Another feature not explored in this article that further leverages this programming model is the use of wrappers to perform specific layout operations, such as adding margins and aligning views in the declarative flow of components. With this, the UIView coding in view code is optimized and easily componentize and adaptable to SwiftUI.

UIViewController

The view controller implementation can be simplified to the extreme and only support configurations that do not include layout. Basically, we’ll have the contentView property and the loadView method override.

In some cases the view controller may contain more screen specific rules and possibly delegate methods, more complex hierarchy, however, this basic implementation model is enough to implement any screen on iOS. In addition, we can make some modifications to support SwiftUI and keep the UIKit core, making the migration to Apple’s new framework much easier.

Figure 4 explores the UserViewController implementation using the contentView variable and overriding the loadView and viewDidLoad. You can notice the controller’s simplicity, no complex methods, and clean code. Ideally, this implementation is always maintained, otherwise, code complexity should be transferred to the controller’s view and subviews, in this case, the UserView.

Figure 4 — UserViewController implementation with contentView.

Furthermore, still on the concept of wrappers, the implementation of the controller and the contentView property can be enhanced, making the code even more effective.

In order not to cover several topics in this article, although this topic is somehow contained in the view code process, it will be discussed separately in another article addressing specific codes that will facilitate the development process.

Implementing UserViewController with SwiftUI support would be done differently. Instead of having a contentView variable, we would have the body with some View.

In loadView, we would use the UIHostingController(rootView:) to wrap the body. Then, with some utility method of the wrappers, we can easily wrap the UIHostingController in a UIView and then finally assign the controller view.

Considerations

The evaluation of the examples in this article should focus only on code templates. It isn’t intended to assemble a working layout and discuss techniques in this regard.

Using these models together with the two protocols discussed, it is possible to create the application interfaces using the concept of view code and declarative programming. Finally, the init methods are commented for the programmer to explore according to the project architecture, leaving them open for each case.

Thanks for the reading!

If you would like to contribute so that I can continue producing more technical content, please feel free to buy me a coffee ☕️ through the Buy me a Coffee platform.

Your support is essential to maintain my work and contribute to the development community.

--

--

Brenno de Moura 🏳️‍🌈

Software engineer with a passion for technology and a focus on declarative programming, experience in challenging projects and multidisciplinary teams