Hello everyone, my name is Artem, I am an iOS developer. Today I want to talk about the approaches to the use of UITableViewController and UICollectionViewController.
It is hardly possible to find a mobile application that does not use a list view of data. We (iOS developers) spend most of our time with a TableView or CollectionView. That is why it is crucial to choose approaches to the use of these basic elements in view of the speed of development and the cost of further supporting the solutions being created. I want to share the findings to which we came with colleagues in Touch Instinct.
The article is intended for developers who work with TableView (CollectionView), but for some reason do not work with TableViewController (CollectionViewController). Further only the TableView (Controller) will be mentioned, but everything written concerns the CollectionView (Controller) too.
Option 1. MassiveViewController
The easiest and most familiar option is when a developer creates a ViewController on a storyboard, places a TableView on it and specifies a ViewController as an object that will implement the UITableViewDelegate and UITableViewDataSource protocols.
')
All is well, but not always. Sometimes, problems arise. And they arise when additional logic appears in this controller, little associated with the TableView. Well, still, if UIBarButtonItem handlers appear in the controller. But, as a rule, everything appears in this controller: button handlers below (above / left / right) TabieView, various pop-up elements, working with the network, configuring cells and so on. There is a gross violation of the principle of Single Responsibility. ViewController is sad ... Other developers are even more sad when they see such a picture.
Option 2. Solid
An ordinary developer after the first meeting with articles about SOLID begin to pursue dubious ideas of varying degrees of severity. One such idea is the universal application of the principle of Single Responsibility. Including, to the detriment of common sense.
Impressed by the read, the developer creates separate objects for the implementation of each protocol, as shown in the diagram below.
But the problem with this approach is that UIKit often makes it difficult to support this principle. In fact, it turns out that closely related things turn out to be on different sides of the barricades. A basic example: when calculating the height of a cell (Delegate), you need to know its content (DataSource). In the end, things may become even more confusing than in the previous version.
Option 3. Semi-Solid
The developer realized that the use of the Solid version is associated with certain difficulties, and decided to slightly simplify it. For this purpose, not two classes are created, but one that implements the Delegate and DataSource protocols. This option is easier to implement, but it has its drawbacks, which is that it is still necessary to create an additional class and provide two-way communication between the ViewController and the object implementing the protocols.
In fact, everything is simple
Once upon a time, we discussed this problem with colleagues. And then one experienced developer said: “What is there to discuss, then? Everything is out of the box. ”
And it really is. For some reason, many developers do not use TableViewController, arguing that there is a separate TableView that you can put on the view as you like. I would like to answer this with two arguments:
- Almost always the TableView will stretch over the entire view - isn’t it easier to use the TableViewController right away?
- If the TableView is not located on the entire view, then there will be other elements, and the code associated with them will appear in the ViewController.
There is nothing good that the TableView code is mixed with the code of other elements for which ViewController is responsible. Imagine how aggravated the picture is when it comes to an iPad application and there are more than two tables on the screen that are processed by a single ViewController.
The approach that I propose to consider is a compromise between solidity and simplicity. It consists in the rejection of separately lying TableView. The latest versions of iOS and Xcode allow you to apply this approach without pain and suffering, with convenience and pleasure.
Many people will not like the idea of ​​producing ViewControllers, but in reality they will only appear where they really need to. For example, the task is to make a table in full screen. It’s better to use a TableViewController for this purpose right away. If you need to add the same table somewhere else, then you can easily reuse this TableViewController, since there will be only table logic and nothing extra.
If you suddenly need to change the location of the TableView, then you simply create a separate ViewController into which the TableViewController is integrated (via ViewController Containment). This solution is so boxed that everything can be done through the storyboard:
Moreover, the integrated ViewControllers will change their size in accordance with the container in which they are integrated, which cannot but please the eye and once again confirms the “blockiness” of this solution.
The same can be done in the code:
let embedController = UIViewController() addChildViewController(embedController) view.addSubview(embedController.view) embedController.view.frame = view.bounds // Auto Layout, embedController.didMoveToParentViewController(self)
Special rubric: question for backfilling!
Why is it not necessary to call
embedController.willMoveToParentViewController(self)
?
Correct answerThis method is called internally.
addChildViewController(embedController)
It should be noted that in case of removal of the embedded ViewController, everything happens the opposite:
embedController.willMoveToParentViewController(nil) embedController.view.removeFromSuperview() embedController.removeFromParentViewController()
And the
embedController.didMoveToParentViewController(self)
method will be called automatically.
We go further.
Honestly, I myself do not want to produce unnecessary ViewControllers, so there are several elements that may not be directly related to the table, but still do not deserve the creation of a separate ViewController:
- BarButtonItems. They can be easily added to the TableViewController and processed there. To do this, you must enable the display of NavigationBar in Simulated Metrics and add a Navigation Item.
- Table cap. Not everyone knows that you can insert a header into the TableView for the entire table, and not just for sections.
A header added this way will scroll along with the table content.
How to live with it now?
If you need to pass something from the ParentViewController to the ChildViewController, you must override the prepareForSegue method.
private var someController: SomeViewController! override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let someController = segue.destinationViewController as? SomeViewController { self.someController = someController } }
Well, quite obvious advice at last
No need to score ViewController with any rubbish. Logic that does not apply to the elements on the screen must be rendered. A good practice, which, fortunately, almost everyone knows about, is to push the cell configuration code to the classes of these cells.
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { if let cell = cell as? SomeCell { cell.textLabel = someObject.someText
Summary
- We do not use a separate tableview.
- We use TableViewController
- You need to add something to the TableViewController - create a ParentViewController
- We do not configure cells directly in the ViewController, but transfer the model to the cell and configure it there
- We apply all the same to the CollectionView