1

I have setup a tableview with dynamic height cells aka UITableView.automaticDimension using Autolayout. This works fine. Now what I am trying to achieve is to change the height of cell & animate it. The issue is that when I change cell height & animate it, the animation is weirdly jumping. The jump only occurs if I scroll down a bit & then expand/collapse cells. I have a simple table view cell. It has a label & an empty UIView with fixed height constraint. When I want to collapse/expand the cell, I simply change the constant of that height constraint to 0 or 300.

I have tried many collapsable tableview examples off the internet. All of them have this issue. One exception is https://github.com/Ramotion/folding-cell, but that uses fixed heights for cells.

I have tried quite a few options to animate the cell height change.

1-> On didSelectRow, I change the height constraint & call tableview beginUpdate & endUpdates. Doesn't solve the jump issue.

2-> Change my model & call tableView.reloadRows. Doesn't solve the jump issue.

This is screenshot of my tableview cell setup. https://drive.google.com/open?id=12nba6cwRszxRlaSA-IhrX3X_vLZ4AWxy

A link to video of this issue: https://drive.google.com/open?id=19Xmc0PMXT0EuHTJeeGHm4M5aPoChAtf3

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self
        tableView.estimatedRowHeight = 50
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedSectionHeaderHeight = 0
        tableView.estimatedSectionFooterHeight = 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! OuterTableViewCell
        let height: CGFloat = isCellExpanded[indexPath.row] ? 300 : 0

        cell.labelText.text = "Cell Number: \(indexPath.row + 1)"
        cell.buttonExpansionToggle.setImage(UIImage(named: isCellExpanded[indexPath.row] ? "arrow-down" : "arrow-right"),
                                            for: .normal)
        cell.viewContainerHeight.constant = height
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }

Another form of didSelectRow:

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        guard let cell = tableView.cellForRow(at: indexPath) as? OuterTableViewCell else { return }
        isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
        let height: CGFloat = self.isCellExpanded[indexPath.row] ? 300 : 0
        cell.viewContainerHeight.constant = height
        cell.layoutIfNeeded()
        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {


            tableView.beginUpdates()
            tableView.endUpdates()

            // fix https://github.com/Ramotion/folding-cell/issues/169
            if cell.frame.maxY > tableView.frame.maxY {
                tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.bottom, animated: true)
            }
        }, completion: nil)
    }

I have also tried to call beginUpdates() & endUpdates() outside animation block, yet the issue persists.

I expect the animation to be smooth. Hope someone can help. If someone can setup a simple demo project on github that would be awesome.

Demo project link: https://gitlab.com/FahadMasoodP/nested-tableviews

Help in any form is appreciated. Thanks.

  • Could you put your sample project? – Thanh Vu Aug 18 '19 at 09:41
  • @ThanhVu I have now included gitlab link of sample project https://gitlab.com/FahadMasoodP/nested-tableviews – Fahad Masood Aug 18 '19 at 09:45
  • Could you please provide height in table delegate method. And in didSelect Method just reload a particular row. See how it goes? – Jayraj Vala Aug 19 '19 at 08:49
  • And as per my experience please don't use estimated row height, use height method instead. – Jayraj Vala Aug 19 '19 at 08:50
  • @JayrajVala I cannot use fixed height. Cell sizes are being auto calculated with Autolayout, as you see in viewDidLoad I have set row height to UITableView.automaticDimension – Fahad Masood Aug 20 '19 at 07:39
  • @FahadMasood no i am not asking you to give fix height. calculate collection cell height as per its content and then calculate table cell height as i said. – Jayraj Vala Aug 21 '19 at 08:52
  • @JayrajVala How do I calculate cell height if cell content is a bit complex (images, labels, buttons etc)? Currently, I am using Autolayout to take care of that. – Fahad Masood Aug 21 '19 at 13:51
  • Which of these have dynamic size? I guess label will have dynamic text. none except label can have dynamic size. so you can calculate label's content and then add remaining fix size for a particular cell. look at this answer https://stackoverflow.com/a/51646488/9391052 – Jayraj Vala Aug 22 '19 at 06:05

1 Answers1

0

My solution is adding dynamic estimate row height. I think it is bug of UIKit. iOS 13 issue will be not occur.

First, You need add property estimateRowHeightStorage to store estimate height by indexpath

var estimateRowHeightStorage: [IndexPath:CGFloat] = [:]

Second, You need store current height of cell for use later

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    estimateRowHeightStorage[indexPath] = cell.frame.size.height
}

Final, you use estimateRowHeightStorage to set estimate row height.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    if estimateRowHeightStorage[indexPath] != nil {
        return estimateRowHeightStorage[indexPath]!
    }

    return 50
}

Run and feel.

I did found new solution in your case. If you hardfix height when expand. Only need change some thing. Replace all code above with estimatedHeightForRowAt function

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let height: CGFloat = isCellExpanded[indexPath.row] ? 373 : 73

    return height
}
Thanh Vu
  • 1,498
  • 8
  • 11
  • Thanks for the answer. I tried this solution & it seemed to work for the first few cells but then I found that the issue is still there. I have updated the project on gitlab so you can check that. If you open last 5-6 cells & then close one by one starting from bottom, you will see that the issue is still there. – Fahad Masood Aug 18 '19 at 10:52
  • Although cell sizes won't be fixed/pre-calculated, yet I tried your updated solution. Doesn't work. This is the video after your update https://drive.google.com/open?id=1EsKr97ZTA1z2V_kmCBsr7bNi-V-cSlje – Fahad Masood Aug 18 '19 at 16:11
  • I tried it on a real device & the issue still occurs – Fahad Masood Aug 21 '19 at 18:24