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
- Added
UITableView
as a subView to theself.view
- Added tableView
dataSource
anddelegate
method and attached appropriate delegates to current class - Added number of rows so that it returns non-zero number
- 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 whentableView
is not visible. Something to know when eitherdataSource
is not called ortableView
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.