1

When people click on Load More, I want to dynamically resize the table view header. What happens is that new content gets added to the table view header when people click on load more, so that height changes. I don't know the new height beforehand.

How can I do this? What I'm doing now is when people click on "Load More", I execute the code below.

@objc func expandDesc(sender: UIButton) {
    loadMoreDesc = !loadMoreDesc
    tableView.reloadData()
}

What can I add to the code above to dynamically resize the table view header?

Chris Hansen
  • 7,813
  • 15
  • 81
  • 165

2 Answers2

1

Auto-layout does not automatically update the size of a table header view, so we need to do it "manually."

We can use this extension to help:

extension UITableView {
    func sizeHeaderToFit() {
        guard let headerView = tableHeaderView else { return }

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height

        var frame = headerView.frame
        
        // avoids infinite loop!
        if height != frame.height {
            frame.size.height = height
            headerView.frame = frame
            tableHeaderView = headerView
        }
    }
}

Now, when we update the content of the table header view - which would cause its height to change - we can call .sizeHeadrToFit()

Here's a complete example:

Simple multiline cell - cyan label

class MultilineCell: UITableViewCell {
    
    let label: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        return v
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(label)
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            // constrain label to the cell's margins guide
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
        ])
        label.backgroundColor = .cyan
    }
    
}

Multiline view for table header - yellow label in a red view

class MyTableHeaderView: UIView {
    
    let label: UILabel = {
        let v = UILabel()
        v.numberOfLines = 0
        return v
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
        let g = self.layoutMarginsGuide
        NSLayoutConstraint.activate([
            // constrain label to the self's margins guide
            label.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
        ])
        // this avoids auto-layout complaints
        let c = label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
        c.priority = UILayoutPriority(rawValue: 999)
        c.isActive = true
        
        backgroundColor = .red
        label.backgroundColor = .yellow
    }
    
}

Example table view controller

class DynamicHeaderTableViewController: UITableViewController {
    
    var theData: [String] = []
    
    let myHeaderView = MyTableHeaderView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 10 rows with 2-to-5 lines per row
        for i in 1...10 {
            let s = "This is row \(i)"
            let n = Int.random(in: 1...4)
            let a = (1...n).map { "Line \($0)" }
            theData.append(s + "\n" + a.joined(separator: "\n"))
        }
        
        tableView.register(MultilineCell.self, forCellReuseIdentifier: "cell")
        
        myHeaderView.label.text = "Select a row..."
        tableView.tableHeaderView = myHeaderView
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        tableView.sizeHeaderToFit()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MultilineCell
        c.label.text = theData[indexPath.row]
        return c
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        myHeaderView.label.text = theData[indexPath.row]
        tableView.sizeHeaderToFit()
    }
}

The above code will generate 10 rows with 2 to 5 lines per row. On didSelectRowAt we'll update the table view header with the text from the row.

Result when first launched:

enter image description here

After selecting "Row 2":

enter image description here

After selecting "Row 3":

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
0

Just use constraints, You can animate them. You don't have to reload whole table view for that. You can keep weak reference to header and change it state, If more is pressed, animate constraints, If it is text You want to fit, use StackView and just add/remove UILabel to stack view and it should work perfectly.

Prettygeek
  • 2,461
  • 3
  • 22
  • 44
  • Can you give me an example? – Chris Hansen Oct 13 '20 at 16:22
  • I already did. Most of this You can achieve by doing said work in storyboard/xib. https://stackoverflow.com/questions/35975444/how-do-i-tell-my-uitableviewcell-to-auto-resize-when-its-content-changes?rq=1 https://www.raywenderlich.com/8549-self-sizing-table-view-cells There is plenty online. – Prettygeek Oct 13 '20 at 16:25