UITableView Gotcha!

Have you ever run into the scenario where you struggle for several hours, fail to recognize the problem, end up blaming the creator of framework but still get amazed at how it works for other people/projects? This is a classical facepalm moment. I run into it more than usual, especially when I am tired or bored to do things which I didn't mean to do.

Recently I ran into similar problem while trying to layout UITableView on the view port. How? I will show you it in a bit. Here's the code.


class ViewController1ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.tableFooterView = UIView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.delegate = self
        tableView.dataSource = self
        self.view.addSubview(tableView)
        let views: [String: AnyObject] = ["tableView": tableView]
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: views))
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: views))
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = "\(indexPath.row)"
        return cell
    }
}

And here's the blank screen

So what's wrong? I have

  1. Added UITableView as a subView to the self.view
  2. Added tableView dataSource and delegate method and attached appropriate delegates to current class
  3. Added number of rows so that it returns non-zero number
  4. Set up project so that nothing is hidden or alpha for any view is set to 0.0

I struggled with this puzzle for couple of hours trying to put plethora of breakpoints and banging my head against Macbook asking myself what's going on. It's all done programmatically, so I cannot even blame xibs or storyboards as usual.

After spending my valuable time behind this seemingly innocuous bug, I realized that it was classical copy-paste fallacy where I mistakenly copy pasted constraint, but forgot to update it. If you look above code carefully, there are two constraints.


self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: views))

self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: views))

However, they both refer to horizontal constraint, so layout engine is still unsure how view should be laid out vertically, so it assumes height to be 0.0 which messes it all up.

Turns out method cellForRowAtIndexPath does not even get called when tableView is not visible. Something to know when either dataSource is not called or tableView is not visible on view

After closer inspection, I changed following line


self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: views))

to


self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: views))

And tableView suddenly appeared on view and method cellForRowAtIndexPath got called too.