Mathieu Rios
Mathieu Rios

Reputation: 386

How to dynamically set tableView height?

I'm having a hard time setting my tableView height according to it's content. My cells have dynamic heights and if I hard code an height for my table view I'm able to see that they are fine. Now my problem is:

All things being said here is some code:

class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    private var cellHeight: CGFloat? = UITableView.automaticDimension
    private var hasLoader: Bool = true
    lazy var tableView: UITableView! = UITableView()
    lazy var loadMoreButton: UIButton! = PrimaryButton(textKey: "actionSeeMore")
    lazy var emptyView: UILabel! = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.separatorStyle = .none
        tableView.rowHeight = UITableView.automaticDimension
        initActions()
        initLayout()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        loadList(appendItems: true)
    }

    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()
            if(self.hasLoader == true) {
                make.bottom.equalTo(self.loadMoreButton.snp.top).offset(-15)
            }
            print("PRINT TABLE HEIGHT", self.tableView.contentSize.height) // It's 0.0
            make.height.equalTo(1000) // Just trying things
        }
        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)
            }
        }
    }
    
    func initActions() {
        loadMoreButton.addTarget(self, action: #selector(self.loadMoreOnClick(sender:)), for: .touchUpInside)
    }


    func loadList(appendItems: Bool) {
        DispatchQueue.main.async {
            self.apiClient.getList(completion: { _ in
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                    // Here I normally do tableView.snp.remakeConstraints { (make) in make.height.equalTo(self.tableView.contentSize.height) } which fires viewDidLoad many times (as much as I have cells)
                }
            })
        }
    }

    func loadMore() {
        self.page += 1
        self.loadList(appendItems: true)
    }

    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)
        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 UITableView.automaticDimension
    }

I have omitted parts of the code for readability.

Upvotes: 1

Views: 2470

Answers (1)

Tarun Tyagi
Tarun Tyagi

Reputation: 10102

Try following :

  1. Declare a variable inside your UIViewController subclass like this.
private var contentSizeObservation: NSKeyValueObservation?
  1. Inside viewDidLoad() or whenever your myTableView is set up, start observing contentSize changes.
contentSizeObservation = myTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] (tv, _) in
    guard let self = self else { return }
    self.myTableViewHeightConstraint.constant = tv.contentSize.height
})
  1. Do NOT Forget to clean this up in deinit call -
deinit {
    contentSizeObservation?.invalidate()
}

This approach will make sure that your myTableView is always as big in height as it's content.

Upvotes: 8

Related Questions