30

I have a UITableView with cells that are dynamically sized. That means I have set:

tableView.estimatedRowHeight = 50.0
tableView.rowHeight = UITableViewAutomaticDimension

Now I want to get the height of the whole table view. I tried getting it through tableView.contentSize.height but that only returns the estimated row height, and not the actual dynamic height of the table view.

How do I get the dynamic height of the whole table view?

leonardloo
  • 1,753
  • 3
  • 20
  • 31

9 Answers9

34

I finally hacked out a solution:

tableView.contentSize.height will not work for dynamic cells because they will only return the number of cells * estimatedRowHeight.

Hence, to get the dynamic table view height, you look for all visible cells, and sum up their heights. Note that this only works for table views that are shorter than your screen.

However, before we do the above to look for visible cells, it is important to know that note we need to get the table view on the screen so that we can obtain visible cells. To do so, we can set a height constraint for the table view to some arbitrarily large number just so it appears on the screen:

  1. Set height of table view constraint:

    // Class variable heightOfTableViewConstraint set to 1000
    heightOfTableViewConstraint = NSLayoutConstraint(item: self.tableView, attribute: .height, relatedBy: .equal, toItem: containerView, attribute: .height, multiplier: 0.0, constant: 1000)
    containerView.addConstraint(heightOfTableViewConstraint)
    
  2. Call tableView.layoutIfNeeded(), and when completed, look for the visible cells, sum up their height, and edit the heightOfTableViewConstraint:

    UIView.animate(withDuration: 0, animations: {
        self.tableView.layoutIfNeeded()
        }) { (complete) in
            var heightOfTableView: CGFloat = 0.0
            // Get visible cells and sum up their heights
            let cells = self.tableView.visibleCells
            for cell in cells {
                heightOfTableView += cell.frame.height
            }
            // Edit heightOfTableViewConstraint's constant to update height of table view
            self.heightOfTableViewConstraint.constant = heightOfTableView
    }
    
leonardloo
  • 1,753
  • 3
  • 20
  • 31
  • Where you are calling this code to update the height constraint of tableView? – Bharat Modi Oct 17 '16 at 09:56
  • 2
    This worked for me, but I had to put my code in `viewDidLayoutSubviews()` because in `viewDidLoad()` the cells haven't been loaded yet. – Jason Brady Feb 28 '17 at 20:19
  • 1
    This is a very valuable answer! I need to change the hight of a tableview for a today extension widget that used self sizing cells (I do this programmatically). The trick was (as you said) to set a height of 1000 to get all the cells expanded, then sum up the cells visible heights and then set the new height with layoutIf needed. Great, thanks. – brainray Jan 20 '18 at 12:24
  • 1
    What if I have multiple sections & needs to get the total tableview height ? – Sneha Mar 28 '18 at 11:51
  • 5
    this kind of stuff is so hacky it gives me anxiety. what is Apple doing??? – Alex Bollbach May 11 '18 at 19:40
  • set the tableview height to 1000 as initial quite weird for first time load (low number of cell, like 2, 3). So I play around with alpha. Work like a charm. – Nizzam Apr 17 '19 at 09:21
12

Above mentioned solutions are pretty well. I tried a different approach & its smooth & clear to understand. Just Copy Paste the code & it will work.

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    tableView.removeObserver(self, forKeyPath: "contentSize")
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "contentSize"{
            if object is UITableView{
                if let newValue = change?[.newKey]{
                    let newSize = newValue as! CGSize
                    heightOfTableViewConstraint.constant = newSize.height
                }
            }
        }
    }
Shubham Singh
  • 159
  • 1
  • 5
  • 3
    Generally the last answer is never my goto answer, but this one saved me a 2 day of work. I was almost on the verge of redoing the whole screen just before app going live. Thanks mate – Maniac One Aug 01 '21 at 18:06
  • aousm solution! – Riddik Jun 20 '22 at 10:06
4

it's already been answered, but I want to share my experience as well.

I have the same problem. I've searched answers in many similar questions and tried their answers and doesn't work.

Finally I've found leonardloo answer. Thanks a lot, but it doesn't solved my problem yet. I just want to complete his answer. I put this code from his answer:

UIView.animate(withDuration: 0, animations: {
self.tableView.layoutIfNeeded()
}) { (complete) in
    var heightOfTableView: CGFloat = 0.0
    // Get visible cells and sum up their heights
    let cells = self.tableView.visibleCells
    for cell in cells {
        heightOfTableView += cell.frame.height
    }
    // Edit heightOfTableViewConstraint's constant to update height of table view
    self.heightOfTableViewConstraint.constant = heightOfTableView
}

in this block of code:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    if indexPath.row == data.count-1{ 
        // Insert the code here
    }
    return cell
}

It means after all the rows in table have been loaded, we update the height of the row using that piece of code. It works!!!

Community
  • 1
  • 1
  • This one works! Thank you Ebed! Just one little issue. You need to do "heightOfTableView += cell.frame.height" one more time after the loop cuz the last cell is not among the visibleCells yet ;) – Ke MA Feb 27 '18 at 22:34
  • here the answer works partially,By doing this we only get the visible cells height but not all the cells – siva kumar Mar 21 '18 at 13:18
0

Even though @leonardloo's answer is partially correct and it may suit the needs of many people, I wanted to share how I fixed my issue.

I had a large tableView with big rows and had the same issue. When I've applied @leonardloo's answer, I've seen that it draws only number of visible cells it has found in the beginning and it draws 1 row whereas it's supposed to draw 5 (my datasource count).

To fix that, I have found such a simple solution:

UIView.animate(withDuration: 0, animations: {
self.tableView.layoutIfNeeded()
}) { (complete) in
    var heightOfTableView: CGFloat = 0.0
    // Get visible cells and sum up their heights
    let cells = self.tableView.visibleCells
    for cell in cells {
        heightOfTableView += cell.frame.height
    }
    // Edit heightOfTableViewConstraint's constant to update height of table view
    // Set tableView's constraint to height + 1 so that 
    // it can still draw the next visible cell it's supposed to.
    self.heightOfTableViewConstraint.constant = heightOfTableView + 1
}

The point here is to let it draw the next cell so that it will be counted in visibleCells as well by setting constraint constant to height + 1. I've tested and it seems working like a charm now.

Yusuf Kamil AK
  • 771
  • 8
  • 17
0

The answers above do not work when you have things like headers, footers, section headers. Using the height of only visible cells is a recipe for disaster. Instead you can use the tableView contentSize height.

UIView.animate(withDuration: 0, animations: {
        self.tableView.layoutIfNeeded()
        }) { (complete) in
            self. heightOfTableViewConstraint.constant = self.tableView.contentSize.height
    }
endrick
  • 55
  • 2
  • 7
-1

You should calculate the height of tableView in the following delegate method,

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    //Get the height required for the TableView to show all cells
    if indexPath.row() == (tableView.indexPathsForVisibleRows.last! as! NSIndexPath).row {
        //End of loading all Visible cells
        float heightRequiredForTable = tableView.contentSize.height

        //If cell's are more than 10 or so that it could not fit on the tableView's visible area then you have to go for other way to check for last cell loaded 
    }
}
Bharat Modi
  • 4,158
  • 2
  • 16
  • 27
  • 1
    I'm trying to get the height of table view so I can set the constraints for the table view programmatically. I don't think the above works.. – leonardloo Oct 17 '16 at 06:58
  • If you want to set the height of the tableView as the height required for all cell then you would need to add UITableView as a child of UIScrollView and then create the heightConstraint and update it in the same delegate methods as explain in my answer to this question - http://stackoverflow.com/questions/38044803/uitableview-not-scrollable-but-enough-height/38045861#38045861 – Bharat Modi Oct 17 '16 at 08:38
-3

Just maintain an array of type CGFLoat and append UITableViewAutomaticDimension in cellForRowAt indexPath delegate of tableview

var cellHeight = [CGFloat]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cellHeight.append(UITableViewAutomaticDimension)
}
Bugs
  • 4,491
  • 9
  • 32
  • 41
-3

I'll add my answer here since I struggled with this a bit.

My table view was using sections that had headers, so just getting the height of the visible cells wasn't working. What I ended up doing was first set a super large height for the tableView so that the content will all be added. Then on the last cell of the tableView, I resize the tableView to be the same height as its content.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  ... // do your normal cell processing

  // determine height and change it
  if indexPath.section == (self.sectionTitles.count - 1) && indexPath.row == (sectionData.count - 1) {

    UIView.animate(withDuration: 0, animations: {
      self.tableView.layoutIfNeeded()
    }) { complete in

      // Edit heightOfTableViewConstraint's constant to update height of table view
      self.tableViewHeightConstraint.constant = self.tableView.contentSize.height
    }
  }
}
Sylvan D Ash
  • 1,047
  • 13
  • 24
-6

you can try this code

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}
Mina Fawzy
  • 20,852
  • 17
  • 133
  • 156