Search code examples
swiftuitableviewautolayoutscrollview

Why set tableView auto height not working?


My Problems: I'm trying to set auto height to my table cell. However, when I using tableView.rowHeight = UITableView.automaticDimension or func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return UITableView.automaticDimension }. As shown on image, I got 0 height for my tableView; but if I set the tableView's height at autoLayout, I can see my tableView again.(which is not right because I want height to be dynamic, the button and the view should at bottom of table view.) enter image description here enter image description here

Here is My ViewController Code:

class ViewController: UIViewController {
    
    var dataSource = ["1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2",]
    
    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.delegate = self
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.contentInsetAdjustmentBehavior = .never
        scrollView.bounces = false
        return scrollView
    }()
    
    private lazy var tableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .plain)
        tableView.backgroundColor = .clear
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(TestTabelCell.self, forCellReuseIdentifier: TestTabelCell.identifier)
        tableView.rowHeight = UITableView.automaticDimension
        return tableView
    }()
    
    private lazy var moreBtn: UIButton = {
        let button = UIButton()
        button.setTitle("Click for More", for: .normal)
        button.setTitleColor(UIColor.green, for: .normal)
        return button
    }()
    
    private lazy var bottomView: UIView = {
        let view = UIView()
        view.backgroundColor = .brown
        return view
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        setupUI()
    }

    func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(tableView)
        scrollView.addSubview(moreBtn)
        scrollView.addSubview(bottomView)
        
        scrollView.snp.makeConstraints { make in
            make.top.leading.trailing.equalToSuperview()
            make.bottom.equalToSuperview()
        }
        
        stackView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        tableView.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.leading.trailing.equalToSuperview()
        }
        
        moreBtn.snp.makeConstraints { make in
            make.top.equalTo(tableView.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(20)
        }
        
        bottomView.snp.makeConstraints { make in
            make.top.equalTo(moreBtn.snp.bottom)
            make.leading.trailing.equalToSuperview()
            make.width.equalTo(390)
            make.height.equalTo(400)
            make.bottom.equalToSuperview()
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: TestTabelCell.identifier, for: indexPath)
        if let cell = cell as? TestTabelCell {
            cell.updateUI(String(indexPath.row))
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        print("print", UITableView.automaticDimension)
        return UITableView.automaticDimension
    }
}

and Here is My Cell Code:

class TestTabelCell: UITableViewCell {
    
    static let identifier = "ParlorJourneyListCell"
    
    private lazy var label: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 12)
        label.textColor = .black
        return label
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: .default, reuseIdentifier: reuseIdentifier)
            setupUI()
        }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupUI() {
        addSubview(label)
        
        label.snp.makeConstraints { make in
            make.top.equalToSuperview().inset(8)
            make.height.equalTo(20)
            make.bottom.equalToSuperview()
        }
    }
    
    func updateUI(_ num: String) {
        label.text = "Test Num: \(num)"
    }
}

Solution

  • Actually, you're calculating just table view cell height, not table view height itself. You have to recalculate table view height every time reloadData() get called. I assume that the cell's autolayout is correct. There are few steps to do:

    func viewDidLoad() {
       ...
       //Added listener to your table view contentSize if changed
       tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
    }
    
    //Then override this function to update table view height
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard keyPath == "contentSize" else { return }
        guard
            let newValue = change?[.newKey],
            let newHeight = (newValue as? CGSize)?.height else { return }
        tableView.snp.updateConstraints {
            $0.height.equalTo(newHeight)
        }
    }