UIViewController Life Cycle Behavior

Yury Buslovsky
7 min readMay 14, 2020
Screenshot of the example app

TL;DR: There is a nice way to reuse the code of view controller’s life cycle’s methods. It comes in handy when an OOP hierarchy can’t seem to help accomplish such refactoring. It is called View Controller Life Cycle Behavior. Fill free to check out an example right away.

Introduction

Once in a while we find out that some of our view controllers’ declarations look messy and oversized due to recurring configuration methods which happen to have been called from nowhere but a life cycle’s method. At the beginning of a project it seems like no big deal, yet it still ends up being something in need of an emergent refactoring. And there are cases where we cannot use a superclass extraction (which is the first thing coming to mind), because not every view controller provides the same combination of functionality or, in other words, behaves like that. I would like to introduce View Controller Life Cycle Behavior.

Abstract behavior

A view controller might be doing different kinds of things simultaneously during its life cycle’s methods’ execution meaning there may be an array of behaviors, which is to be stored in some property. Hence we will have to provide a protocol — an abstract behavior:

  1. Our goal is to try and reuse life cycle’s functionality, so it makes sense to include counterparts of viewDidLoad(), viewWillAppear(_:), etc., in the protocol. Besides, we’re going to have to know about the current “behavioral” view controller (e.g., to obtain access to its properties), so we pass it in each time. Every method is given a default empty implementation as they should not be required, because we don’t override every available method in our custom view controllers.
  2. Now we need to find a way to embed our methods’ calls into original ones of a view controller. Of course, we could manage to do it using a good old superclass, but, at this rate, we would not be able to call super.viewDidLoad() until after we have added a behavior in self.viewDidLoad() (otherwise applying a behavior which executes something once the view have been loaded would be impossible, because we can’t really pass in anything custom into the initializers of a view controller displaying content), exposing the array of behaviors to every derived class at that. A private child view controller to the rescue! We just provide a public method in view controller’s extension. It receives needed behaviors, then creates an instance of LifeCycleBehaviorViewController (which wraps all passed in behaviors and executes their methods at each life cycle’s stage respectively) and adds it as a child. Thus, its life cycle synchronizes with the parent’s one.

The child view controller is just a dummy, which is why its view must be hidden. It will not affect life cycle’s methods’ calling.

Example

I will demonstrate 2 close-to-real behavior examples in a pretty simple abstract app. The behaviors are able to be implemented using either concrete objects or protocols. You can find examples of the former here, and of the latter here.

Let’s assume you have an app where view controllers are embedded in a navigation controller, and you want them to have navigation bars with some primary color (implying some of them use the default color meaning you cannot utilize a superclass, as the coloring of the bar may be randomly combined with other behaviors). Moreover, you, say, decide to support iOS 10, so you’ll have to deal with translucent bar’s issues (sometimes, there will be a juncture when some table views will have a peculiar top inset on iOS versions where there’s no such thing as Safe Area. Those are properties influencing this behavior. One of the easiest solutions here is setting edgesForExtendedLayout to an empty array). So, off and on, you’ll have to rectify those situations in view controllers.

Concrete object implementation

Let’s look at aforementioned behaviors’ implementations:

This behavior colors a navigation bar along with all its subviews keeping nice contrast
This behavior helps to get rid of the weird space above some table views on older iOS versions

Then, all we have left to do is add the behaviors to a view controller:

The status bar should be light, because I’ve chosen blue as the primary color of the app. It also can be refactored a little further, as it’s shown at the bonus section.

Protocol implementation

There’s a way to somewhat refactor the previous implementation: we can use a generic view controller superclass which will add passed in behaviours on its own, relieving subclasses of that. The problem is, there’s no “arrays of generic types”, so we are not able to do this:

class SomeVC: SuperVC<[SomeBehavior, AnotherBehavior, ...]> { ... }

The solution is creating objects comprising several behaviors at once. It’s easily done using them as protocols! Let’s modify existing behaviors a bit:

If you find yourself needing a stored property inside of the behavior, just add to the protocol’s declaration and provide it with a value within a concrete implementation.

The combination:

It’s as simple as this: we just transform behaviors into protocols, then create implementations, make them conform to behaviors, producing all combinations we want.

Now it’s time to provide a base view controller class which is going to manage all these behaviors:

To be able to do the instantiation, don’t forget to declare init() in the ViewControllerLifeCycleBehavior protocol.

That is how our view controllers look like now:

final class FirstViewController:
BehaviorContainingVC<PrimaryNavBar>
{ ... }
final class SecondViewController:
BehaviorContainingVC<PrimaryNavBar_EdgesForExtendedLayout>
{ ... }

And that’s it, you don’t have to do anything else in order to reuse this functionality. There are 2 issues/caveats:

  1. The naming of combinations is awful and there’s no clean way to resolve this. You might use nested generic objects receiving 1 behavior, 2 behaviors, etc., but it seems to be only worse.
  2. You’ll have to resolve protocols’ default implementations’ conflicts. Imagine, you set edges for extended layout in viewWillAppear(_:)’s counterpart (where all navigation bar’s work happens as well). You’ll get this quite vague error:

As a matter of fact, it tells you that the compiler can’t decide whose default implementation to choose — both protocols provide one, not having unambiguous type constraints in their extensions. The only way out is to resolve it manually :(. You can extract the functionality, though. It’ll lead to sufficiently concise solution. Namely:

We extract conflicting functionality into a separate method

Then, we resolve the conflict with help of the new methods:

You can check this solution here.

Despite those issues, I still believe, it’s a nice way to refactor your view controllers’ life cycle’s functionality, because, regardless of what’s going on under the hood, the controllers look clean due to the encapsulation of quite a big portion of UI and/or business logic.

Bonus

There is a way to extract status bar logic into the view controller superclass:

If you don’t like such coupling between base superclass and a specific behavior, feel free to go further, add a flag to the base behavior protocol (something like “affectsStatusBarStyle”) and use it in the condition here.

Conclusion

This approach really helps when you don’t want to “paint” your controllers in storyboards, or when some of your view controllers trigger analytics’ events and so on. You have more than one option to implement it, so it’s a pretty flexible solution worth considering!

Thanks for reading!

--

--

Yury Buslovsky

 iOS Software Engineer @ Revolut • 🇵🇹 Porto, Portugal