I'm not sure if I'm approaching this in the most optimal way.
(1) First, I want a tableView with an arbitrary number of cells, whose dynamic cell height varies (based on content from web server, cannot be calculated ahead of time). But I want the tableView's height to only grow as much to fit all the cell's content (not any taller or shorter). This table view will NOT be scrollable (it's parent's parent view will be).
(2) Then, I want the tableView nested inside a parent UIView.
I mostly got (1) working, although I don't know if my method is optimal or hackish. But I definitely don't have (2) working :(
(Pic below) Turqoise view is the parent of the table View. Cells (in red) and the table size (in white) is correct, but the parent view is shorter than it should be.
After my code change, I have the opposite problem. The parent height as grown to about the right height, but the tableView got chopped off.
This is the code change:
View hierarchy:
If I don't try to put the tableView in a parent UIView, the table "size to fits" just right:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var tableView: UITableView!
@IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet var parentView: UIView!
@IBOutlet var parentViewHeightConstraint: NSLayoutConstraint!
let data = ["this is a short line.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Excepteur sint. deserunt mollit anim id est laborum.",
"another short line.",
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia."
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 50
} // viewDiDload
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// If I add this new frame for the table's parent view,
// the table view height gets chopped off. See 3 lines below
var newFrameForParent = self.parentView.frame
newFrameForParent.size.height = self.tableView.contentSize.height
self.parentView.frame = newFrameForParent
// But if I comment out the above 3 lines, the table view
// height is fine (table height fits actual dynamic content size)
// but the table's parent view is shorter than the table view
var newFrame = self.tableView.frame
newFrame.size.height = self.tableView.contentSize.height
self.tableView.frame = newFrame
print("viewDidAppear: tableView contentSize w: \(tableView.contentSize.width) h: \(tableView.contentSize.height)")
} // viewDidAppear
override func viewWillLayoutSubviews() {
super.updateViewConstraints()
self.tableViewHeightConstraint.constant = self.tableView.contentSize.height
//self.parentViewHeightConstraint.constant = self.tableView.contentSize.height + 20
print("viewWillLayoutSubviews: tableView contentSize w: \(tableView.contentSize.width) h: \(tableView.contentSize.height)")
} // viewWillLayoutSubviews
override func viewWillAppear(_ animated: Bool) {
//tableView.sizeToFit()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath) as! CustomCell
cell.cellLabel?.text = data[indexPath.row]
print("cell \(indexPath.row) height is: \(cell.frame.height)")
return cell
}
}
The full code base is here: https://github.com/jayliew/UITableViewSizeToFitDynamicHeightCells/tree/master/TableViewFitContent
I'm not sure what I need to know, that I don't know, but I suspect it's something around fully understanding how views are laid out, how AutoLayout comes into play, with the viewController lifecycles like viewWillAppear, viewWillLayoutSubviews, etc. (how each stage impacts the laying out of views, with or without AutoLayout). I don't just want to solve this problem, but understand the architecture more holistically and would appreciate it if someone could point me in the right direction.
Answering my own question:
The lesson I learned was not to mix AutoLayout with manually creating frames and changing views to using those frames.
I learned that I had to first artificially set the tableView to a ginormous height, e.g. 1000, so that the dynamic cells would be "visible" on the table. Then only can I loop through all those cells, get the frame height, manually add them all up, before setting the AutoLayout height constraint's constant on the tableView to equal the sum of all the cell's height.
Also, the above had to happen inside of viewDidAppear()
That was it! Sample code here: https://github.com/jayliew/UITableViewSizeToFitDynamicHeightCells
Thanks to @ddustin on Github