1

I have a UICollectionViewCell which has a UTableView inside it. I want to calculate the height of UITableView dynamically based on the content inside it. It means, the UITableView shouldn't scroll but should increase/decrease its height according to its content.

rmaddy
  • 314,917
  • 42
  • 532
  • 579

2 Answers2

0

You can use a self sizing table view like this:

class SelfSizingTableView: UITableView {

    override var intrinsicContentSize: CGSize {
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }

    override var contentSize: CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

}

In addition you have to make sure to set up constraints in your collection view cell correctly (a full set of constraints from the top of the collection view cell to the bottom of it).

André Slotta
  • 13,774
  • 2
  • 22
  • 34
0

I've been there. There are multiple ways to do that, especially if your rows really have constant height, that should be easy. Multiply the number of rows to the constant height, voila, you have your tableView height.

HOWEVER, if you have dynamic cell height of the tableView that is inside the collectionViewCell or tableViewCell (they're the same), then you need another approach.

My approach to that is observing the keyPath contentSize. This one is perfect, I've been using this in my main production project. Here's a full block of the code that I use, including the comments/documentation ;)

/**
 Important Notes, as of 12/18/2018, 9:41PM, a eureka moment:
 - No need for label height.
 - Needs a reference for tableViewHeight.
 - After observing the newSize data, update the constraint's offset of the tableViewHeight reference.
 - And then let know the controller to not reload the data of the tableView but rather begin and end updates only.
 - beginUpdate() and endUpdate() lets the tableView to update the layout without calling the cellForRow, meaning without calling the setupCell method.
*/
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let obj = object as? LLFTableView, obj.tag == 444 {
        if obj == self.tableView && keyPath == "contentSize" {
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                // Edit heightOfTableViewConstraint's constant to update height of table view
                llfPrint("New Size of the tableView: \(newSize) ✅✅✅✅✅")
                DispatchQueue.main.async {
                    self.constraint_TableViewHeight?.update(offset: newSize.height)
                    self.delegate?.reloadData()
                }
            }
        }
    }
}

So, what happens in the controller or viewModel that implements such reloadData delegate method? It calls another delegate method to just let know the controller (That holds the super tableView) that we're updating the height of the cell.

self.tableView.beginUpdates()
self.tableView.endUpdates()

That's it! :) I hope this helps!

Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95