Lighter UIViewControllers with DataProvider

Guilherme Silva Lisboa
Samsao
Published in
5 min readApr 21, 2016

Once upon a time, VCs were huge

When we first started developing mobile applications, everything was still a bit new and it seems as if there was not so much engineering good practices used. Mobile application development is still kind of new but it seems that there is a lot more people trying to develop better code for the mobile platforms.
One of the main issue for the iOS platform is the famous massive view controller (or MVC) problem. If you have not read about it, the main issue is that it is really easy to end up with a view controller that is huge and manages lots of different things. Many options are available (MVVM, MVP, VIPER), you can read more about it on this great post. Independently of your architectural choice, one easy thing to fix is the delegates of table and collection views.

Table views and collection views

As a beginner, when writing code for a table view or a collection view, you are more than likely to put the delegates in your view controller.

class SomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

This might seem like a good option, specially since Apple themselves do so in their examples.
The problem is: We barely declared it and it already implemented 2 protocols.
Whenever a delegate/data source is added to a view controller more code comes along, which means more responsibilities, bigger view controllers and harder maintenance.

Let’s stop you right there. Can we abstract the code related to table view from the VC? How can we make this code reusable to easily setup a table view with it’s content and let the controller take care of only it’s responsibility and nothing else?

Yes, it’s possible, but a lot of boiler plate code will be created. Can we do better?
Let’s try writing together a small example in 2 ways and see how it goes. Ready?

Digging deeper

The scope of our test case will be simple: We have a company named Samsao, which has few employees.
So here’s the goal: List everybody that works there in a UITableView, but with one condition, the founders are displayed in a different cell than employees. Let’s go for it:

First lets create our Controller and declare its properties

class RegularViewController: UIViewController, UITableViewDataSource {

private var people : [Person]!
private var founders : [Person]!
var tableView : UITableView = UITableView()
}

There it is again, we barely created our controller and it’s already a data source for this table view.

Now we have to configure it right? Let’s do it in a method we can call on viewDidLoad or wherever you feel like setting this up

func configureTableView() {
tableView.registerClass(PersonCell.self, forCellReuseIdentifier: kCellReuseID)
tableView.registerClass(FounderTableViewCell.self, forCellReuseIdentifier: kFounderCellReuseID)
tableView.dataSource = self

people = Samsao.employees
founders = Samsao.founders

tableView.reloadData()
}

Is it over? If you look at your compiler you’ll see you have some errors, ‘RegularViewController does not conform to protocol …’, let’s fix them

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var person : Person!
var cell : PersonCell!

if indexPath.section == 0 {
person = founders[indexPath.row]
cell = tableView.dequeueReusableCellWithIdentifier(kFounderCellReuseID) as! PersonCell
} else {
person = people[indexPath.row]
cell = tableView.dequeueReusableCellWithIdentifier(kCellReuseID) as! PersonCell
}

cell.textLabel?.text = person.name
cell.imageView?.image = person.image

return cell
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return founders.count
case 1:
return people.count
default:
return 0
}
}

Done? Samsao might be hiring, so what if they hire someone new? Let’s add a method to cover this case then, insert the new employees!

func addPeople() {
let initialPosition = people.count
people.appendContentsOf(Samsao.newEmployees())

var indexPaths : [NSIndexPath] = []
for i in initialPosition..<people.count {
indexPaths.append(NSIndexPath(forRow: i, inSection: 1))
}
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)
}

Now we finally have our Controller that can display all the company as we wanted!

Wait a minute. Didn’t we say we could abstract this logic and do better than this?

We thought a bit about it at Samsao and the result was the DataProvider!
Let’s redo our example using it.

class SamsaoTableController: UIViewController{  
var tableView : UITableView = UITableView()
var provider : TableViewProvider!
}

Ok, here is our view controller! A bit cleaner right?

Now time to configure our provider!

func configProvider() {
//Here we create the founders items and section
let fItems = ProviderItem.itemsCollectionWithData(Samsao.founders, cellReuseIdentifier: kFounderCellReuseID)
let fSection = ProviderSection(items: fItems)

//Here we create the employees items and section
let eItems = ProviderItem.itemsCollectionWithData(Samsao.employees, cellReuseIdentifier: kCellReuseID)
let eSection = ProviderSection(items: eItems)

//Here we configure what cells or provider will use... (you can also configure with nibs)
let eConfig = ProviderConfiguration(reuseIdentifier: kCellReuseID, cellClass: PersonCell.self)
let fConfig = ProviderConfiguration(reuseIdentifier: kFounderCellReuseID, cellClass: FounderTableViewCell.self)

//Finally we create our provider!
self.provider = TableViewProvider(withTableView: self.tableView, sections: [fSection,eSection], delegate: nil, cellConfiguration: [eConfig,fConfig])
}

It’s done! you can call this method in the same place you were calling configureTableView() and remove all datasource code, you’ll see everything is there!

But we can still remove some boilerplate code from our addPeople(), lets ask our provider to add someone for us

//Here we create our items. Each item has it cellReuseID, so you can specify its cell class, simple and easy!
let items = ProviderItem.itemsCollectionWithData(Samsao.newEmployees(), cellReuseIdentifier: kCellReuseID)
self.provider.addItemsToProvider(items, inSection: 1)

There it is!
Let’s look back at our controller now. It’s now only responsible for UIViewController code! All it knows is that it has a UITableView and a TableViewProvider. All the UITableView logic is abstracted by the provider!

Current limitations and future improvements

We tried to abstract all that makes sense and keep the library as lean as possible so no one would need to write an article ‘Lighter data providers with…’, but we are sure there are things to be implemented or improved!
Feel free to take a look at our issues and participate on this!

Also keep in mind that Both Provider classes, TableViewProvider and CollectionViewProvider, can be easily subclassed if you need a specific behaviour for your case.

Conclusion

I hope that this article and the provider help the iOS community to improve code quality and move towards lighter view controllers.
We have been using it for a while internally at Samsao and it saved us few hours in both writing and maintaining the code

Thank you for your time and remember, DataProvider is there on Github awaiting for your contribution and questions!

Don’t hesitate to comment or share your experience in the comment section below.

If you want to know more about our digital agency, please visit our website or reach us here.

--

--