Clean Code for Multiple Storyboards

Even though storyboards have been around iOS5, many developers are reluctant to use them, specially in large projects. Though I prefer using storyboards, I’ve to admit developers despising storyboards aren’t totally wrong.

Problem 1: For a larger project, storyboards often become unwieldy and unmanageable. Have look at a crazy one below

Crazy Storyboard

Problem 2: Storyboards are extremely Slow. As they grow in size, they don’t only become unhandy for developers but also for XCode. Once you tap on a storyboard file of this size, you can move out of your seat, roam around cafeteria, grab a cup of coffee, say hi to everyone and come back. You’ll be in good luck if XCode is not stuck & has loaded the storyboard successfully.

Problem 3: Merge Conflicts in Version Control. It’s always a painful experience when multiple developers are working on a project as it almost always leads to merge conflicts in storyboards.

Problem 4: One is never able to focus on particular feature of the app.

Problem 5: I’ve not heard this from many developers, but faced this issue in one of my projects. Using a huge storyboard leads to very slow loading for the application. Splitting it into multiple storyboards solved the issue for me.


Inspite of the issues above, I feel we can use storyboards in a good, clean manner, resolving most of the above. I also feel having a storyboard as large as one above, doesn’t fulfil it’s purpose as much as it defeats. A storyboard is meant to explain a story, not a saga. An app’s storyboard can be easily divided into multiple storyboards, with each one representing an individual story. Best example will be Pre-Login flow. Splash Screen, Login, Signup, Forgot Password, T&C etc can be easily seen as a separate flow independent of the functionalities in the app.

Multiple storyboards solve problem 1, 2, 4 and 5 as the solution is designed specifically for this. Problem 3 is solved to a large extent as merge conflicts will occur only when multiple developers work on same storyboard. One can find numerous blogs, tutorials on using multiple storyboards approving the idea.

Apple also provided it’s seal of approval on the idea, with introducing storyboard references with iOS9. Here’s a great tutorial on the same, So I won’t repeat anything from there.

Enters Linus Torvalds : “Talk is cheap, Show me the code!”
ME : OK Boss 😏

TL;DR

It’s a good idea to use multiple storyboards in a project, let’s see how we can do it in a clean way.


Spoiler Alert : A quick glance at what we are going to achieve.

Concise, Expressive, Type-safe, No need for typecasting, No String literals !

Step 1 : AppStoryboard Enumeration

enum AppStoryboard : String {
    case Main = "Main"
case PreLogin = "PreLogin"
case Timeline = "Timeline"
    var instance : UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
}
}
// USAGE :

let storyboard = AppStoryboard.Main.instance

// Old Way

let storyboard = UIStoryboard(name: “Main”, bundle: Bundle.main)

AppStoryboard is an enum with raw type String, because Storyboards are instantiated by their names i.e. String literals. This enum should list all the storyboards in the project as it’s cases. It’s more like a namespace for the storyboards in the project. Using such an enumeration, makes it convenient for a developer/code-reviewer to see what all storyboards are there in the project. More importantly, it saves you from instantiating storyboards using string literals and you can leverage autocomplete feature to get storyboard instances. No scope for typos !

I always try my best to avoid String literals in the code as Compiler doesn’t warn for typos in String literals and it can lead to annoying bugs and crashes. You shall never underestimate the power of humans to err!

If you make a pact that you’ll be using the same storyboard name as the name of cases in enum, you can re-write the enum in a more concise manner. You can skip raw values as for string type enums, case name is the implicit raw value.

enum AppStoryboard : String {
    case Main, PreLogin, Timeline
    var instance : UIStoryboard {
return UIStoryboard(name: self.rawValue, bundle: Bundle.main)
}
}

Using enum AppStoryboard, one can instantiate storyboards as below:

let mainStoryboard = AppStoryboard.Main.instance
let preLoginStoryboard = AppStoryboard.PreLogin.instance
let timelineStoryboard = AppStoryboard.Timeline.instance
// Instantiate ViewController
let loginScene = preLoginStoryboard.instantiateViewController(
withIdentifier: "LoginVC")
// OR One-liner
let loginScene = AppStoryboard.PreLogin.instance
.instantiateViewController(withIdentifier: "LoginVC")

Better than what we needed to do earlier at least. Right.


Step 2 : Extending UIViewController

Having write down storyboard identifier each and every time instantiating a ViewController is also error-prone as it includes string literals. Now, it’s time to get rid of this also. Let’s put a static computed property in the ViewControllers as below:

static var storyboardID : String {
return "<Storyboard Identifier>"
}

Now one can access the storyboard identifier as

<ViewController_Name>.storyboardID
// e.g. LoginVC.storyboardID

Bu that wouldn’t be handy, Right. Most of the developers, use a standard practice to either use same name for ViewController class and storyboard identifier or appending it with a suffix i.e. “_ID”. This is a good practise as it can save you from putting a static computed property in all the ViewControllers in your project, by extending UIViewController class by this method. Let’s see how you can do that.

Let’s assume one is using same name for class name and storyboard identifier. If one’s using suffix “_ID” for storyboard identifier. “\(self)” can be replaced by “\(self)” + “_ID”.
extension UIViewController {
   class var storyboardID : String {
return "\(self)"
}
}
Please note that, I’ve used “class” instead of “static” because static properties/methods cannot be overridden by subclasses to provide custom storyboard identifier (if needed).
One might be tempted to use Swift Reflection API, in order to get the right invoking class name in the extension, which has been achieved simply by using “self” here. Have a look at the gist in to view the alternatives.

Now, one can instantiate a ViewController as

let loginScene = AppStoryboard.PreLogin
.viewController(viewControllerClass: LoginVC.self)

Good : No More String Literals. Use XCode Autocomplete.

Bad : Lengthy. Inferred type of the object is UIViewController but LoginVC.

What I don’t like about this, is, here loginScene is inferred to be the type of UIViewController. So in order to access LoginVC’s properties, you have to forcefully typecast it.

let loginScene = AppStoryboard.PreLogin
.viewController(viewControllerClass: LoginVC.self)
as! LoginVC

Secondly, It’s lengthy and having to forcefully typecast it pretty much takes it’s beauty away. So now, let’s fix typecasting and put some beauty into this.😉


I’ll switch over to code snapshots from here onwards, rather than putting the code in Code blocks as larger code is getting a bit unreadable in it.

Taking it to next level

Let’s empower AppStoryboard to instantiate ViewControllers on it’s own by placing another method in it.

This function takes class name as argument and returns it’s instance. Using this, one can instantiate a ViewController as

Though it’s a bit succinct, it still doesn’t solve the problem of forceful typecasting. To solve this, we need to take some help of Generics. Let’s re-write the function now, with generic arguments and return type and place it in enum.

AppStoryboard would look like this now

T refers to the generic argument, T.Type means Data type of T. Now one can instantiate a ViewController like below

Neat. Right 🙃. Generics comes to my rescue more often than not!

Though I would have loved to skip “self” from “LoginVC.self”. Unfortunately, doesn’t work that way.

For sake of completeness, let’s also wrap “instantiateInitialViewController()” method of UIStoryboard in the AppStoryboard enum.

Great. That’s all we could do with AppStoryboard enum but as I was still not satisfied with the result, I decided to take it a bit further. Hence, Coming back to UIViewController’s extension.

Creating a static method for instantiating ViewController in UIViewController’s extension would really simplify instantiation process for ViewControllers. Let’s give it a try.

Notice the return type, i.e. “Self”. Self refers to the type of the current "thing" inside of a protocol. I was very happy to see it works in an extension also.

Now one can instantiate ViewController as below

Great!!! This looks a lot prettier.😍 The way I wanted.🤓

As the method takes an argument of type enum AppStoryboard, we can leverage Swift type inference by just putting “.PreLogin” instead of “AppStoryboard.PreLogin”.

Complete code for AppStoryboard and UIViewController’s extension can be found in the gist. I’m using XCode 8, Swift 3 for the demo.

First comment would provide usage examples, similar to below

That’s it you have it. Nice, clean way of using multiple storyboards without using String literals and forceful typecasting all over the place.

I would love to hear what you think about the idea. Please leave comments. Thanks 😊

EDIT : Also read my new article on #colorLiterals and #imageLiterals in Swift3. Hope you’ll like it !
https://medium.com/@gurdeep060289/color-image-new-literals-in-the-cocoa-town-7ef4f2710194#.chndavqsi

Follow me on Twitter : https://twitter.com/Gurdeep060289