XIB vs Code: How to properly use UITableViewCells in Swift
Photo by Glenn Carstens-Peters on Unsplash
UITableView is one of the most used UI components in Swift, since most of the modern apps require some kind of list to display similar items, be it products, contacts or photos.
For this purpose, an UITableViewCell represents an atom of a list, defining a blueprint of how a single item of that list should look like. Well, things can become complicated real quick since it turns out there are multiple ways to create an UITableViewCell in Swift.
In this article, we’ll explore different ways a cell can be created and registered inside a table view. What we’ll cover:
- How to create & register a NIB from a XIB file
- How to create & register an UITableView subclass created programatically
- How to reuse the programatic cell inside a Storyboard.
Ready? Let’s dive in! 🏊♀️
1. Initial project setup
- Create a new iOS project and give any name you wish. Make sure to select Storyboard as the interface, instead of SwiftUI.
- For simplicity, leave all the generated files as they are and open Main.storyboard.
- Add a button, give it a cute name, open ViewController.swift and connect an IBAction for the Touch Up Inside event. We’ll use this action to show the table view class.
- Create a new Swift file called TableController and make it a subclass of UITableViewController.
2. Using XCode’s interface builder to create a NIB
- Create a new Cocoa Touch Class, subclass of UITableViewCell and make sure to check the "Also create XIB file" checkbox.
- Open the newly created XIB file, drag an UILabel onto it and setup its constraints.
- Connect the label to the nib’s Swift file by creating an IBOutlet connexion for it. Your setup should look like this:
- Moving back to the TableController, it’s time to register this class. Inside viewDidLoad(), we do so like this:
let nib = UINib(nibName: "NIBTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: NIBTableViewCell.cellIdentifier)
where cellIdentifier is a static constant we defined inside the NIBTableViewCell class.
The ‘nibName’ used here in the UINib constructor must match the exact file name of the nib, otherwise the system won’t be able to locate the NIB inside the main bundle.
- Implement the required UITableView methods in order to see something on screen:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: NIBTableViewCell.cellIdentifier, for: indexPath)
return cell
}
- Open ViewController and inside the button action method add the logic to display the table view screen modally:
@IBAction func openTableAction(_ sender: UIButton) {
let tableController = TableController()
present(tableController, animated: true)
}
- Run the project an a simulator, tap the button and you should be able to see the table view correctly.
3. Create an UITableViewCell subclass programatically
Let’s see now the other method how we can achieve the same result, by implementing the table view’s cell in a programatic manner.
- First, create a new Swift file and make it subclass UITableViewCell.
- Implement the two required initializers:
init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
required init?(coder: NSCoder)
- Create a setup() method where we’ll setup the view and add the required constraints:
func setup() {
contentView.addSubview(label)
label.text = "Programatic Cell Label"
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
])
}
Here I’ve set a different text for the label, just to make it clear we’ll be using a different cell.
-
Call the setup() method inside every initializer.
-
Back inside TableController, we comment the old nib registering and replace it with the following code:
tableView.register(ProgramaticCell.self, forCellReuseIdentifier: ProgramaticCell.cellIdentifier)
- Run the app again and you should see the new cell is displayed. 🎉
4. Use the programatic UITableViewCell inside a Storyboard
With the table view cell now created, we can reuse it in many places throughout our code, including in a Storyboard file.
-
Create a new Storyboard file called Table, delete the generated View Controller Scene and drag a Table View Controller object.
-
Open Identity Inspector on this table controller and set its custom class as our TableController and set an unique Storyboard ID, which we’ll be using next. I’ve named it conveniently TableVC.
-
Select Table View Cell from the interface builder on the left and do the same, i.e set its custom class to our ProgramaticCell class.
-
Now we have two options: we could either continue to register our cell programatically, using the register method we wrote earlier inside TableController, or we do it inside the Storyboard. Even though I prefer the first version, for the purpose of this tutorial, let’s do it using the storyboard.
-
Select Table View Cell from the interface builder again and open the Attributes Inspector. On the Identifier row, write an unique id, like ProgramaticCellIdentifier. Now you are free to delete the register method since it’s no longer needed. 🗑️
-
Now that we’ve linked our Storyboard with our custom classes, let’s head back to the ViewController class to change our view controller creation.
-
Replace the code inside openTableAction with:
let storyboard = UIStoryboard(name: "Table", bundle: nil)
let tableController = storyboard.instantiateViewController(withIdentifier: "TableVC")
present(tableController, animated: true)
- Finally, run the simulator one more time to see the result.
5. Bonus section: refactoring
You might have noticed that for every cell definition, either for the NIB or the programatic class, we had to define an unique identifier in order for the UITableView to be able to identify it properly. This can become cumbersome, and without type safety it can lead to unexpected developer errors.
Lucky for us, we can create an extension for the UITableViewCell class to generate an unique reuseIdentifier for us, based on the class name, like this:
extension UITableViewCell {
static var reuseIdentifier: String {
return String(describing: self)
}
}
This way we don’t need to come up with unique ideas of our own, we have a safe way to access it and we can safely say all the identifiers will be unique.
In this way, we:
-
don’t need to come up with unique ideas of our own
-
have a safe way to access the reuse identifier
-
can say with certainty that all the identifiers are unique
Pretty neat, right?
The full demo project can be found here.