📜 ⬆️ ⬇️

We do UITableView. For beginners

New Year's holidays have passed, but my desire to write useful and not-so articles is not! Today we will talk about UITableView , working with UITableViewDataSource and reusing cells. We will touch on how to install a root controller without a storyboard, errors when working with a table, a layout and a large header for a UINavigationBar .

For those who like unfunny jokes, I recorded a video on YouTube . Well, here everything will be serious. Let's start.

Create an empty project, call it whatever you want and go to the controller. UIKit has a class UITableViewController . You can google a lot of tutorials where the table is shown exactly in the context of this class. But for more understanding, we'll do everything in the base UIViewController .

Most often, when a table is needed, a UINavigationController is used:



Let's add it. In the file AppDelegate , the function didFinishLaunchingWithOptions insert the following code:

let navigationController = UINavigationController.init(rootViewController: ViewController()) self.window = UIWindow.init(frame: UIScreen.main.bounds) self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() 

A short educational program for what: storyboards and constraints are good , but in this tutorial we will try to do without them. This code will allow you to ignore the storyboard (you can delete it) and wrap the ViewController in a UINavigationController .

It would be nice to set the title for the UINavigationBar . To do this, we will go to the ViewController class (we will do all further work here) and add the following code to the viewDidLoad method:

 self.view.backgroundColor = UIColor.white self.navigationItem.title = "Table" self.navigationController?.navigationBar.prefersLargeTitles = true 

Now the headline will be big and fashionable. Having started the project, we will see the following:



Making a tableview


Preparatory actions are completed, we can move on to the main thing. In the controller, create a UITableView . Choose an initializer that has a Style parameter. Frame set any, we will return to it later. And for style, install Grouped .

 let tableView = UITableView.init(frame: .zero, style: UITableView.Style.grouped) 

Why I’m having such a style name, I have no idea, but it will allow us to make a native table, as in the Settings application. If you need a non-stylized table - use the initializer only with the frame parameter.



Layout


Here we were saved by the constraints, but we will not look for easy ways. I will show the way that I use. He, of course, does not claim to be canonical and does not have to do just that. Let's declare a function that will set the frame of the views, and the parameter will take the size of the controller:

 private func updateLayout(with size: CGSize) { self.tableView.frame = CGRect.init(origin: .zero, size: size) } 

You need to call the function in two places - in the viewDidLoad method and in viewWillTransition :

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { (contex) in self.updateLayout(with: size) }, completion: nil) } 

Now the table at any orientation will be placed on the whole screen. You can add other processing to the updateLayout method.

We do UITableViewCell


Since the tutorial is not about cells, but the table, we will not dwell on customization in detail. Let's make a cell class inherited from the base one:

 class TableViewCell: UITableViewCell { } 

To use a cell in a table, you need to register a class. To do this, in ViewController, we call the following method on the table:

 self.tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell") 

If the class is clear, then the identifier deserves attention. In 99% of cases the rule will work:

- " Cells of one class must have one identifier "

There are very few situations where cells of the same class need different identifiers, and they are mostly associated with drawing code and animations.

Datasource


This is the property that points to the object that will fill the table. Those. implement the UITableViewDataSource protocol. Two methods are required:

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {} func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} 

The first is responsible for the number of cells in the section. We still have one section, a multisection (is there such a word?) Will not be considered. The second method is for getting the cell object. It works slyly, do not think that you saw a fighter!

But first, let's add an array of strings to fill the table with.



We now turn to the implementation of the first method:

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch tableView { case self.tableView: return self.data.count default: return 0 } } 

We tell the table that in the 0th section there will be as many cells as there are elements in the data array. The second method is a bit more complicated. Just initialize the cell does not work, and all because of the system reuse cells. But do not scold Apple, in fact it is good! To get an object, call the following code:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = self.tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell cell.textLabel?.text = self.data[indexPath.row] return cell } 

The dequeueReusableCell method will get the cell object by its identifier, and we will do a type conversion using as to the TableViewCell class, which should be the cell. textLabel is the base class property, no additional configuration is required.

It remains to specify the dataSource for the table and the viewDidLoad method should now look like this:

 override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white self.navigationItem.title = "Table" self.navigationController?.navigationBar.prefersLargeTitles = true self.view.addSubview(self.tableView) self.tableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell") self.tableView.dataSource = self self.updateLayout(with: self.view.frame.size) } 

If we run the project, we will see a table filled with content:



Reuse


While it seems that everything is in order. Let's add a disclosureIndicator for one of the cells. This is an accessory that you definitely met in iOS:



Let's say the task is to set the indicator only for the first cell. The first thing that comes to mind is to add a simple if to the cellForRowAt method:

 if indexPath.row == 0 { cell.accessoryType = .disclosureIndicator } 

But the enemy is hiding! Let's run the project, and scroll the table off the screen:



The indicator appeared for the first, but haphazardly appears for other cells! This is a reuse - a cell is taken, which is more conveniently stored in memory (an excellent explanation for beginners, isn’t it?) And is configured according to the method. Sometimes it happens that a cell with an indicator is drawn. As a result, we have such a bug. What to do?

It's simple. There are two solutions. The first is in the else block and in essence implies the unambiguous configuration of any cell. The method will look like this:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = self.tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell cell.textLabel?.text = self.data[indexPath.row] if indexPath.row == 0 { cell.accessoryType = .disclosureIndicator } else { cell.accessoryType = .none } return cell } 

There is another way - to implement the prepareForuse method on the cell. As the name implies - the method is called before reuse. All you need is to reset the cell to default. The code looks like this:

 class TableViewCell: UITableViewCell { override func prepareForReuse() { super.prepareForReuse() self.accessoryType = .none } } 

There are many nuances associated with re-use. For example, the task of loading images for a cell in the background with pagination, adaptive height, animated deletion and insertion. But about this in the second part, if my hands reach me)

For seekers


I try to regularly record a tutorial on my channel . You can find videos how to draw code or how to make the controller of the Apple Music player , as well as the video for this article:

Source: https://habr.com/ru/post/436390/