1

I built a generic UITableView to which I can pass a model and a custom UITableViewCell and it's working well except for two things: the cells height and the UITableView height.

I guess these two issues are related.

I'm trying multiple things I found on SO to help with it but nothing worked out.

Here is a sample of my generic UITableView:

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.register(Cell.self, forCellReuseIdentifier: "Cell")

    tableView.dataSource = self
    tableView.delegate = self
    tableView.separatorStyle = .none

    loadList(appendItems: true)

    initActions()
    initLayout()
}

func initLayout() {
    self.view.addSubview(tableView)
    self.view.addSubview(emptyView)
    self.view.addSubview(loadMoreButton)

    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.snp.makeConstraints { (make) -> Void in
        make.left.right.leading.trailing.top.equalToSuperview()
    }
    tableView.isScrollEnabled = false

    emptyView.translatesAutoresizingMaskIntoConstraints = false
    emptyView.isHidden = true
    emptyView.textAlignment = .center
    emptyView.snp.makeConstraints { (make) -> Void in
        make.left.right.leading.trailing.equalToSuperview()
        make.top.equalToSuperview().offset(8)
    }

    self.loadMoreButton.isHidden = true
    if(self.hasLoader == true) {
        self.loadMoreButton.isHidden = false
        loadMoreButton.translatesAutoresizingMaskIntoConstraints = false
        loadMoreButton.snp.makeConstraints { (make) -> Void in
            make.centerX.equalToSuperview()
            make.top.equalTo(tableView.snp.bottom)
        }
    }
}

Then, when my data is loaded I do:

self.tableView.reloadData()
self.tableView.snp.remakeConstraints { (make) -> Void in
    make.left.right.leading.trailing.top.equalToSuperview()
    if(self.hasLoader == true) {
        make.bottom.equalTo(self.loadMoreButton.snp.top)
    }
    make.height.equalTo(self.tableView.contentSize.height)
}

Then, my tableView funcs:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return resourceList.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
    let currentResource = resourceList[indexPath.row]
    cell.setResource(resource: currentResource) // Here I initialize my labels, UIImage, etc.
    cell.isUserInteractionEnabled = true
    self.cellHeight = cell.contentView.frame.size.height // Something I tried
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    self.tableView.deselectRow(at: indexPath, animated: true)
    let resource = resourceList[indexPath.row]
    if (resource.getRouteParam() != "") {
        router.setRoute(routeName: resource.getRouteName(), routeParam: resource.getRouteParam())
    }
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return self.cellHeight ?? 200 // Here I'm trying to return an *automatic* height of my custom cell
}

To complete I can say that my cells have different sizes due to different labels length so I can't define a static height.

I also tried to add estimatedHeightForRowAt in my viewDidLoad method but it didn't work either.

When I use UITableView.automaticDimension my cells have always a height of 44.0

Witek Bobrowski
  • 3,749
  • 1
  • 20
  • 34
Mathieu Rios
  • 350
  • 1
  • 17

1 Answers1

1

1. UITableView configuration

To be able to have automatically sized cells in UITableView you will need to do set the following properties on your table view:

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200 // or other value

2. Proper AutoLayout inside of UITableViewCell

Make sure yo have auto layout setup correctly in your cells.

3. (Optional) UITableViewDelegate and estimatedHeightForRowAt

Implement this method of UITableViewDelegate to provide the table with with more accurate size, so it will correctly handle fast scrolling.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return 200 // try to calculate more accurate size
}

Some stuff I noticed

  1. This will not really work how you expect it to work so don't bother with it. Here the cells views are not even correctly laid out, so there is a very high chance you will get problematic values (like 0)
self.cellHeight = cell.contentView.frame.size.height
  1. There should be no need to resetting the constraints after you reload data. Setting them in the viewDidLoad should be enough.
self.tableView.reloadData()
self.tableView.snp.remakeConstraints

Resources

How to make UITableViewCells auto resize to their content

Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Witek Bobrowski
  • 3,749
  • 1
  • 20
  • 34
  • i think `estimatedHeightForRowAt` it also need to return `UITableView.automaticDimension` – jatin fl Jul 08 '21 at 09:55
  • 2
    There is no need to implement `estimatedHeightForRowAt` at all, but if you do, you should return value you estimate based on cells content or cell type or other parameters suited for your case. The documentation states: "Return automaticDimension if you have no estimate." – Witek Bobrowski Jul 08 '21 at 10:03
  • I succeeded in making the UITableViewCells the correct height: the issue was in having a proper setup inside my cells. Now my UITableView height is not setting up to fit the contentSize. – Mathieu Rios Jul 08 '21 at 12:54
  • you mean `UITableView` height != `UITableView.contentSize.height`? This is not how table view works, it's content size (height) will stay independant of table views own size. It's content size is defined by the number of cells and their sizes – Witek Bobrowski Jul 08 '21 at 12:57