HomeiOS DevelopmentUITableView tutorial in Swift - The.Swift.Dev.

UITableView tutorial in Swift – The.Swift.Dev.


This information is made for inexperienced persons to study the foundations of the UITableView class programmatically with auto structure in Swift.

UIKit

The best way to create a desk view programmatically?

Let’s leap straight into the coding half, however first: begin Xcode, create a brand new iOS single view app challenge, enter some title & particulars for the challenge as typical, use Swift and at last open the ViewController.swift file immediately. Now seize your keyboard! ⌨️

Professional tip: use Cmd+Shift+O to rapidly leap between information

I am not going to make use of interface builder on this tutorial, so how will we create views programmatically? There’s a technique known as loadView that is the place it’s best to add customized views to your view hierarchy. You may choice+click on the strategy title in Xcode & learn the dialogue about loadView technique, however let me summarize the entire thing.

We’ll use a weak property to carry a reference to our desk view. Subsequent, we override the loadView technique & name tremendous, with the intention to load the controller’s self.view property with a view object (from a nib or a storyboard file if there may be one for the controller). After that we assign our model new view to an area property, flip off system supplied structure stuff, and insert our desk view into our view hierarchy. Lastly we create some actual constraints utilizing anchors & save our pointer to our weak property. Simple! 🤪

class ViewController: UIViewController {

    weak var tableView: UITableView!

    override func loadView() {
        tremendous.loadView()

        let tableView = UITableView(body: .zero, fashion: .plain)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(tableView)
        NSLayoutConstraint.activate([
        self.view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: tableView.topAnchor),
            self.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
            self.view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor),
            self.view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
        ])
        self.tableView = tableView
    }
}

All the time use auto structure anchors to specify view constarints, if you do not know the right way to use them, verify my structure anchors tutorial, it is takes solely about quarter-hour to study this API, and you will not remorse it. It is an especially great tool for any iOS developer! 😉

You would possibly ask: ought to I exploit weak or sturdy properties for view references? I would say in many of the circumstances if you’re not overriding self.view it’s best to use weak! The view hierarchy will maintain your customized view by a robust reference, so there isn’t any want for silly retain cycles & reminiscence leaks. Belief me! 🤥


UITableViewDataSource fundamentals

Okay, we’ve an empty desk view, let’s show some cells! So as to fill our desk view with actual information, we’ve to adapt to the UITableViewDataSource protocol. By way of a easy delegate sample, we are able to present numerous info for the UITableView class, so it will to know the way a lot sections and rows might be wanted, what sort of cells ought to be displayed for every row, and plenty of extra little particulars.

One other factor is that UITableView is a very environment friendly class. It’s going to reuse all of the cells which might be at present not displayed on the display, so it will devour means much less reminiscence than a UIScrollView, if you must take care of lots of or 1000’s of things. To assist this conduct we’ve to register our cell class with a reuse identifier, so the underlying system will know what sort of cell is required for a selected place. ⚙️

class ViewController: UIViewController {

    var objects: [String] = [
        "👽", "🐱", "🐔", "🐶", "🦊", "🐵", "🐼", "🐷", "💩", "🐰",
        "🤖", "🦄", "🐻", "🐲", "🦁", "💀", "🐨", "🐯", "👻", "🦖",
    ]

    override func viewDidLoad() {
        tremendous.viewDidLoad()

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

        self.tableView.dataSource = self
    }
}

extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
        return self.objects.rely
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
        let merchandise = self.objects[indexPath.item]
        cell.textLabel?.textual content = merchandise
        return cell
    }
}

After including just a few strains of code to our view controller file, the desk view is now in a position to show a pleasant listing of emojis! We’re utilizing the built-in UITableViewCell class from UIKit, which comes actually helpful if you’re good to go along with the “iOS-system-like” cell designs. We additionally conformed to the info supply protocol, by telling what number of objects are in our part (at present there is just one part), and we configured our cell contained in the well-known cell for row at indexPath delegate technique. 😎


Customizing desk view cells

UITableViewCell can present some primary components to show information (title, element, picture in several types), however often you will want customized cells. Here’s a primary template of a customized cell subclass, I will clarify all of the strategies after the code.

class MyCell: UITableViewCell {

    override init(fashion: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        tremendous.init(fashion: fashion, reuseIdentifier: reuseIdentifier)

        self.initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        self.initialize()
    }

    func initialize() {

    }
    
    override func prepareForReuse() {
        tremendous.prepareForReuse()

    }
}

The init(fashion:reuseIdentifier) technique is a good place to override the cell fashion property if you’re going to use the default UITableViewCell programmatically, however with completely different types (there isn’t any choice to set cellStyle after the cell was initialized). For instance when you want a .value1 styled cell, simply cross the argument on to the tremendous name. This fashion you may profit from the 4 predefined cell types.

You may additionally must implement init(coder:), so it’s best to create a standard initialize() perform the place you can add your customized views ot the view hierarchy, like we did within the loadView technique above. If you’re utilizing xib information & IB, you should utilize the awakeFromNib technique so as to add additional fashion to your views by the usual @IBOutlet properties (or add additional views to the hierarchy as effectively). 👍

The final technique that we’ve to speak about is prepareForReuse. As I discussed earlier than cells are being reused so if you wish to reset some properties, just like the background of a cell, you are able to do it right here. This technique might be known as earlier than the cell goes to be reused.

Let’s make two new cell subclasses to mess around with.

class DetailCell: UITableViewCell {

    override init(fashion: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        tremendous.init(fashion: .subtitle, reuseIdentifier: reuseIdentifier)

        self.initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        self.initialize()
    }

    func initialize() {
        
    }

    override func prepareForReuse() {
        tremendous.prepareForReuse()

        self.textLabel?.textual content = nil
        self.detailTextLabel?.textual content = nil
        self.imageView?.picture = nil
    }
}

Our customized cell can have a giant picture background plus a title label within the middle of the view with a customized sized system font. Additionally I’ve added the Swift brand as an asset to the challenge, so we are able to have a pleasant demo picture. 🖼

class CustomCell: UITableViewCell {

    weak var coverView: UIImageView!
    weak var titleLabel: UILabel!

    override init(fashion: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        tremendous.init(fashion: fashion, reuseIdentifier: reuseIdentifier)

        self.initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        self.initialize()
    }

    func initialize() {
        let coverView = UIImageView(body: .zero)
        coverView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(coverView)
        self.coverView = coverView

        let titleLabel = UILabel(body: .zero)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(titleLabel)
        self.titleLabel = titleLabel

        NSLayoutConstraint.activate([
            self.contentView.topAnchor.constraint(equalTo: self.coverView.topAnchor),
            self.contentView.bottomAnchor.constraint(equalTo: self.coverView.bottomAnchor),
            self.contentView.leadingAnchor.constraint(equalTo: self.coverView.leadingAnchor),
            self.contentView.trailingAnchor.constraint(equalTo: self.coverView.trailingAnchor),

            self.contentView.centerXAnchor.constraint(equalTo: self.titleLabel.centerXAnchor),
            self.contentView.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor),
        ])

        self.titleLabel.font = UIFont.systemFont(ofSize: 64)
    }

    override func prepareForReuse() {
        tremendous.prepareForReuse()

        self.coverView.picture = nil
    }
}

That is it, let’s begin utilizing these new cells. I will even let you know the right way to set customized top for a given cell, and the right way to deal with cell choice correctly, however first we have to get to know with one other delegate protocol. 🤝


Fundamental UITableViewDelegate tutorial

This delegate is accountable for many issues, however for now we will cowl only a few fascinating features, like the right way to deal with cell choice & present a customized cell top for every objects contained in the desk. Here’s a fast pattern code.

class ViewController: UIViewController {

    override func viewDidLoad() {
            tremendous.viewDidLoad()

            self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
            self.tableView.register(DetailCell.self, forCellReuseIdentifier: "DetailCell")
            self.tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")

            self.tableView.dataSource = self
            self.tableView.delegate = self
    }
}
extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        let merchandise = self.objects[indexPath.item]
        cell.titleLabel.textual content = merchandise
        cell.coverView.picture = UIImage(named: "Swift")
        return cell
    }
}

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 128
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let merchandise = self.objects[indexPath.item]

        let alertController = UIAlertController(title: merchandise, message: "is in da home!", preferredStyle: .alert)
        let motion = UIAlertAction(title: "Okay", fashion: .default) { _ in }
        alertController.addAction(motion)
        self.current(alertController, animated: true, completion: nil)
    }
}

As you may see I am registering my model new customized cell lessons within the viewDidLoad technique. I additionally modified the code contained in the cellForRowAt indexPath technique, so we are able to use the CustomCell class as an alternative of UITableViewCells. Do not be afraid of pressure casting right here, if one thing goes mistaken at this level, your app ought to crash. 🙃

There are two delegate strategies that we’re utilizing right here. Within the first one, we’ve to return a quantity and the system will use that top for the cells. If you wish to use completely different cell top per row, you may obtain that too by checking indexPath property or something like that. The second is the handler for the choice. If somebody faucets on a cell, this technique might be known as & you may carry out some motion.

An indexPath has two fascinating properties: part & merchandise (=row)


A number of sections with headers and footers

It is attainable to have a number of sections contained in the desk view, I will not go an excessive amount of into the main points, as a result of it is fairly simple. You simply have to make use of indexPaths with the intention to get / set / return the correct information for every part & cell.

import UIKit

class ViewController: UIViewController {

    weak var tableView: UITableView!

    var placeholderView = UIView(body: .zero)
    var isPullingDown = false

    enum Fashion {
        case `default`
        case subtitle
        case customized
    }

    var fashion = Fashion.default

    var objects: [String: [String]] = [
        "Originals": ["👽", "🐱", "🐔", "🐶", "🦊", "🐵", "🐼", "🐷", "💩", "🐰","🤖", "🦄"],
        "iOS 11.3": ["🐻", "🐲", "🦁", "💀"],
        "iOS 12": ["🐨", "🐯", "👻", "🦖"],
    ]

    override func loadView() {
        tremendous.loadView()

        let tableView = UITableView(body: .zero, fashion: .plain)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(tableView)
        NSLayoutConstraint.activate([
            self.view.safeAreaLayoutGuide.topAnchor.constraint(equalTo: tableView.topAnchor),
            self.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: tableView.bottomAnchor),
            self.view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor),
            self.view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
        ])
        self.tableView = tableView
    }

    override func viewDidLoad() {
        tremendous.viewDidLoad()

        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
        self.tableView.register(DetailCell.self, forCellReuseIdentifier: "DetailCell")
        self.tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")

        self.tableView.dataSource = self
        self.tableView.delegate = self
        self.tableView.separatorStyle = .singleLine
        self.tableView.separatorColor = .lightGray
        self.tableView.separatorInset = .zero

        self.navigationItem.rightBarButtonItem = .init(barButtonSystemItem: .refresh, goal: self, motion: #selector(self.toggleCells))
    }

    @objc func toggleCells() {

        swap self.fashion {
        case .default:
            self.fashion = .subtitle
        case .subtitle:
            self.fashion = .customized
        case .customized:
            self.fashion = .default
        }

        DispatchQueue.major.async {
            self.tableView.reloadData()
        }
    }

    

    func key(for part: Int) -> String {
        let keys = Array(self.objects.keys).sorted { first, final -> Bool in
            if first == "Originals" {
                return true
            }
            return first < final
        }
        let key = keys[section]
        return key
    }

    func objects(in part: Int) -> [String] {
        let key = self.key(for: part)
        return self.objects[key]!
    }

    func merchandise(at indexPath: IndexPath) -> String {
        let objects = self.objects(in: indexPath.part)
        return objects[indexPath.item]
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return self.objects.keys.rely
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
        return self.objects(in: part).rely
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let merchandise = self.merchandise(at: indexPath)
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        cell.titleLabel.textual content = merchandise
        cell.coverView.picture = UIImage(named: "Swift")
        return cell
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection part: Int) -> String? {
        return self.key(for: part)
    }

}

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 128
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        let merchandise = self.merchandise(at: indexPath)
        let alertController = UIAlertController(title: merchandise, message: "is in da home!", preferredStyle: .alert)
        let motion = UIAlertAction(title: "Okay", fashion: .default) { _ in }
        alertController.addAction(motion)
        self.current(alertController, animated: true, completion: nil)
    }
}

Though there may be one fascinating addition within the code snippet above. You may have a customized title for each part, you simply have so as to add the titleForHeaderInSection information supply technique. Yep, it seems like shit, however this one will not be about U&I/X. 😂

Nevertheless if you’re not happy with the structure of the part titles, you may create a customized class & use that as an alternative of the built-in ones. Right here is the right way to do a customized part header view. Right here is the implementation of the reusable view:

class HeaderView: UITableViewHeaderFooterView {

    weak var titleLabel: UILabel!

    override init(reuseIdentifier: String?) {
        tremendous.init(reuseIdentifier: reuseIdentifier)

        self.initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        tremendous.init(coder: aDecoder)

        self.initialize()
    }

    func initialize() {
        let titleLabel = UILabel(body: .zero)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addSubview(titleLabel)
        self.titleLabel = titleLabel

        NSLayoutConstraint.activate([
            self.contentView.centerXAnchor.constraint(equalTo: self.titleLabel.centerXAnchor),
            self.contentView.centerYAnchor.constraint(equalTo: self.titleLabel.centerYAnchor),
        ])

        self.contentView.backgroundColor = .black
        self.titleLabel.font = UIFont.boldSystemFont(ofSize: 16)
        self.titleLabel.textAlignment = .middle
        self.titleLabel.textColor = .white
    }
}

There may be only some issues left to do, you must register your header view, identical to you probably did it for the cells. It is precisely the identical means, besides that there’s a separate registration “pool” for the header & footer views. Lastly you must implement two extra, however comparatively easy (and acquainted) delegate strategies.




extension ViewController: UITableViewDelegate {

    

    func tableView(_ tableView: UITableView, heightForHeaderInSection part: Int) -> CGFloat {
        return 32
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection part: Int) -> UIView? {
        let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderView") as! HeaderView
        view.titleLabel.textual content = self.key(for: part)
        return view
    }
}

Footers works precisely the identical as headers, you simply must implement the corresponding information supply & delegate strategies with the intention to assist them.

You may even have a number of cells in the identical tableview primarily based on the row or part index or any particular enterprise requirement. I am not going to demo this right here, as a result of I’ve a means higher answer for mixing and reusing cells contained in the CoreKit framework. It is already there for desk views as effectively, plus I already coated this concept in my final assortment view tutorial publish. You must verify that too. 🤓


Part titles & indexes

Okay, in case your mind will not be melted but, I will present you two extra little issues that may be fascinating for inexperienced persons. The primary one relies on two extra information supply strategies and it is a very nice addition for lengthy lists. (I favor search bars!) 🤯

extension ViewController: UITableViewDataSource {
    

    func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        return ["1", "2", "3"]
    }

    func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
        return index
    }
}

If you’re going to implement these strategies above you may have somewhat index view to your sections in the precise facet of the desk view, so the end-user will be capable of rapidly leap between sections. Similar to within the official contacts app. 📕


Choice vs spotlight

Cells are highlighed if you find yourself holding them down together with your finger. Cell goes to be chosen when you launch your finger from the cell.

Do not overcomplicate this. You simply must implement two strategies in you customized cell class to make every little thing work. I favor to deselect my cells immediately, if they don’t seem to be for instance utilized by some form of information picker structure. Right here is the code:

class CustomCell: UITableViewCell {

    

    override func setSelected(_ chosen: Bool, animated: Bool) {
        self.coverView.backgroundColor = chosen ? .pink : .clear
    }

    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
        self.coverView.backgroundColor = highlighted ? .blue : .clear
    }
}

As you may see, it is ridiculously simple, however many of the inexperienced persons do not know the way to do that. Additionally they often neglect to reset cells earlier than the reusing logic occurs, so the listing retains messing up cell states. Don’t be concerned an excessive amount of about these issues, they will go away as you are going to be extra skilled with the UITableView APIs.

That is it for now, subsequent time I will proceed with some superior desk views!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments