Controlling UIViewController complexity in Swift with Pokemon
Summary
I find a really common desire in the indie iOS development community is to stop UIViewController subclasses from becoming gigantic. I can definitely see why: UIViewController does a lot and becomes the dumping ground for lots of capabilities. With that in mind, I tried to think critically about what should go into a view controller and what should not go into a view controller.
If we think about MVC, then the view controller should really only be doing three things:
Displaying Data from the Model
Handling Input from the View
Performing CRUD operations on the Model based on the input
That means, as long as we keep the interaction with the views and the models to 1 or 2 liners in the view controller, we’ll have a nice, compact, view controller.
However, Apple threw a big wrench into the works. It is also the responsibility of view controllers to present and dismiss other view controllers.
I think this is where the complexity tends to multiply
The Problem of the Initial View Controller in Storyboards
I find that Storyboards encourage you to work with view controllers in a certain way. The storyboard wants you to have an “Initial View Controller.” This view controller tends to be gigantic because its the main display of your app.
It tends to know about all the other view controllers that your user can browse to while using your app so that it can present them.
It knows about your model because thats usually the main scree of your app.
The storyboard also encourages lots of ancillary view controllers that do things like Adding things / editing things / settings / etc.
I find these controllers are generally pretty light.
Segues can reduce the load on the Initial View Controller, because the Segues handle presenting view controllers by themselves.
However, usually the Initial View Controller needs to configure segues before they can occur.
Existing Suggestions
Helper Classes: This is a big one. I use this a lot, even in the solution we’re going to be talking about. Helper classes can sit in-between the model and controller or the view and the controller and help format things. This makes it easier to keep code in the view controller down to 1 liners.
Functional Reactive Programming / MVVM: FRP is a big one and I really want to learn FRP. However, its a big change over how Apple does things and the way I’ve learned. It also requires big Swift frameworks that make keeping up to date with Swift versions really hard.
Flow Controller: The flow controller is essentially an object that lives “above” any view controller. The flow controllers job is to keep track of which view controller is on screen and help switch from one to another.
My Suggestion - Control Complexity with Multiple Subclasses
We’ve all created subclasses before. We do it every time we make a custom view controller. I find that subclassing not once but 2, 3 or even 4 times can really help separate concerns, control mutable state, and keep your code files really small.
As you add new capabilities to your view controller, you just add a new subclass somewhere in the inheritance tree.
Properties and methods can be marked as private. This helps control mutable state and side effects between subclasses.
Any critical events that multiple subclasses need to know about can be done with methods that the subclass overrides: e.g. a “dataUpdated()” method for when the data source changes.
You can spend as much time as you like making the higher up subclasses more re-usable: e.g. a subclass that displays data in a table view does not need to know about custom data types. They could be represented with a simple protocol.
Swift is here to help!
Immutability: Swift has immutability. You can mark data and helper classes and other objects that the view controller needs as constants and then subclasses cannot change them.
Access Control: Swift has access control. Any subclass can mark methods and properties as private and then no other subclasses or superclasses even know they exist.
Explicit Overrides: Swift requires that overridden methods and properties be declared as such. This makes refactoring mistakes almost impossible: the compiler won’t let you continue until you fix the problems in the subclass.
Catch all the Complexity with Pokemon!
We’re going to create a Pokemon app. This app is going to have capabilities that most apps have. Its going to download a list asynchronously. Its going to allow viewing more detail of a Pokemon. Its going to allow creating and deleting Pokemon. Our initial view controller already has 3 responsibilities.
Async CRUD capabilities for the data (and showing loading states)
Showing Data in a TableView
Presenting new view controllers for adding and viewing Pokemon.
Data Source
First we need a basic data source. This one is going to use a Pokemon API I found online. This data source has a private method that downloads all the Pokemon and calls a completion handler when finished.
It has all 4 CRUD methods, but I didn’t implement the update() one.
The read() method just calls the private method that downloads all the Pokemon.
Where do we place this class? A singleton? On the view controller? I don’t like either of those solutions.
I just add it as a property on the App Delegate.
This allows the data source to be reset in emergencies (unlike a singleton)
This also removes it from the lifecycle of the view controller.
Bottom View Controller and Storyboard
We need a Bottom View Controller file. This view controller file will have no code. It will always inherit from the one of the subclasses of our Pokemon View Controller. The basic point of this VC is so that we never have to change our Storyboard. We can continually insert subclasses into the middle and never have to change the class registered in the storyboard.
Set up a table view in the view controller. Also add a UIActivityIndicatorView that the view controller can start and stop the the Pokemon are downloading.
First 2 Subclasses
Now we need a CRUDViewController and a TableViewController. Note, not UIKit’s UITableViewController. I named mine JSBTableViewController so we don’t mix them up.
Connect the IBOutlet for the UITableView in the storyboard to your table view controller. Then connect the IBOutlet for the UIActivityIndicatorView to the CRUDViewController. Yes, you can do this. Interface builder is awesome! :)
The TableViewController is going to do the work of displaying Pokemon in the tableview. The CRUDViewController is going to read the data from the data source. Look at the screenshot below to see how the concerns are separated.
Notice how the TableViewController does not know where the data came from or how it got there. It just knows its data changed and then updates the UI accordingly.
Also notice how we created a copy of the data for the view controller. Since Swift Arrays are Structs and the Pokemon type we defined is a Struct, we are getting a completely unique copy of the data for the view controller. That way, the data source can keep doing whatever it wants and it won’t interfere with the view controller.
Keep in mind that CRUD operations on the Data Source may need to be more sophisticated because when doing this data copy because the view controller data and the data source data can get out of sync.
CRUD Protocols
I’m not going to implement Update in this sample code, but Create and Delete are easy. In the CRUDViewController file we can define two protocols, AddPokemonDelegate and DeletePokemonDelegate. The CRUDViewController will conform to these protocols.
Then any Add / Delete / Update view controllers will get a reference to the CRUDViewController. This reduces tight coupling between the ancillary view controllers and your data source. It also makes it so the only object changing the data is the view controller displaying the data. This avoids having to deal with being notified when the data changes.
Segue Handling View Controller
We need a view controller that knows how to prepare the ancillary view controllers to be presented. Storyboards give us an easy way to do this, -prepareForSegue:sender:. However, add this to either the CRUDViewController or the TableViewController doesn’t make sense. This has nothing to do with either. So lets make a SegueViewController that inherits from CRUDViewController
This SegueViewController has one job: It knows what the possible segues are and its tightly coupled with other types of view controllers. It knows how to populate the needed data from the data source to new view controllers.
Add and Detail View Controllers
Now in the storyboard we can create an Add Pokemon Scene and a Pokemon Details Scene and create the associated segues needed segues.
Each of these view controllers needs very little code. They each need their associated IBOutlets. Then they need a weak delegate property. The delegate property should be one of the CRUD Protocol types.
Add Swipe to Delete
Since we already did all the work to make deleting Pokemon possible, lets enable swipe to delete.
The question is where should we implement -tableView:commitEditingStyle:forRowAtIndexPath:? Well, its up to you. The TableViewController knows about tableviews. But the CRUDViewController knows how to delete things.
I would implementing it in CRUDViewController because it knows how to delete things.
Finished App
Next Steps
Whats next? Think of something else a view controller should do and implement that as a new subclass.
First run experience:
Don’t implement all of the first run experience in a new file.
Instead, use the new file/subclass to present the view controllers related to the first run experience.
State restoration:
If you wanted your view controller to participate in state restoration, you could add a new subclass (near the bottom) that knows about all of its super classes state and saves and restores it.
You could also implement this on each subclass so each one is only responsible for its own state.
Either way, its way better than bloating an already bloated view controller with tedious state restoration code like we would have to do if the view controller was in one giant class.
Conclusion
Ok!, we’ve learned to embrace the fact that Apple has seen fit for view controllers to do a bit too much in the iOS world. Yet, we’ve managed to separate concerns, keep our code clean, and control state.
Remember, don’t go crazy with this. This is just another tool in the tool belt. If you find a single view controller is getting more than 5 or 10 subclasses deep, you probably need to rethink your architecture.
Pokemon Sample Code Repo on Github
Feedback
Thanks for taking the time to read this really long post. I hope you enjoyed it.
I love hearing feedback. Especially if you dislike the idea and why. I just ask that you be civil.
I can most easily be reached on Twitter:
http://www.twitter.com/jeffburg









