How to Obtain a Dynamic Table View Section Header Height Using Auto Layout

Is it possible to obtain a dynamic table view section header height using Auto Layout?

This is possible. It is new right alongside the dynamic cell heights introduced in iOS 8.

To do this, use automatic dimension for the section header height, and if desired you can provide an estimated section header height. This can be done in Interface Builder when the table view is selected or programmatically:

Table view configuration in storyboard

tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 38

//You can use tableView(_:heightForHeaderInSection:) and tableView(_:estimatedHeightForHeaderInSection:)
//if you need to support different types of headers per section

Then implement tableView(_:viewForHeaderInSection:) and use Auto Layout to constrain views as desired. Be sure to fully constrain to UITableViewHeaderFooterView's contentView, especially top-to-bottom so the height can be determined by the constraints. That's it!

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {    
let headerView = UITableViewHeaderFooterView()
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.backgroundView = {
let view = UIView()
view.backgroundColor = myCustomColor
return view
}()

let headerLabel = UILabel()
headerLabel.translatesAutoresizingMaskIntoConstraints = false
headerLabel.text = "Hello World"
headerView.contentView.addSubview(headerLabel)

NSLayoutConstraint.activate([
headerLabel.leadingAnchor.constraint(equalTo: headerView.contentView.leadingAnchor, constant: 16),
headerLabel.trailingAnchor.constraint(equalTo: headerView.contentView.trailingAnchor, constant: -16),
headerLabel.topAnchor.constraint(equalTo: headerView.contentView.topAnchor, constant: 12),
headerLabel.bottomAnchor.constraint(equalTo: headerView.contentView.bottomAnchor, constant: -12)
])

return headerView
}

UITableView header dynamic height in run-time

This is how i have approached it

Using tableview
I have created the UI For the header in XIB

Now in the following delegate method

func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

}

I Create a UIView for the header and calculate the height based on the content and return the same.

Now i can return the same header view from the following delegate method

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

}

Based on the section i again create a view from xib and return that view from the method.

In my case i needed only one headerview for table so i kept 2 sections and returned the headerview for section one.

Dynamic height of header with multiline UILabel in UITableView

Assuming you have got your Section Header setup using a XIB file, it is probably a constraint issue that is causing incorrect sizes, among other things.

I have put together a list of common errors that can happen when working with dynamic section headers. Please check them one-by-one, to see which one resolves your issue.

1. Make sure your Constraints are setup correctly.

Open up your Section Header XIB and make sure that there are enough constraints for UITableView to calculate the view's height. Notice that I have setup the bottom constraint with a priority of 999. This is important because of Step 3.


2. Make sure your UILabel can grow

Set the "Number of Lines" of both UILabels to zero as shown below.


3. Adjust the Content Hugging and Compression Resistance

Select your UILabels, and click the "Size Inspector" ().

At the bottom, set these values to 1000 so that Auto Layout treats them as important as your other constraints.

  • Content Hugging Priority > Vertical

This sets how well the UILabel's height should hugs its text content.

  • Compression Resistance Priority > Vertical

This sets how much the UILabel should resist shrinking its height beyond the height of its content.

Like so:


4. Return a reasonable value in estimatedHeightForHeaderInSection

func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 44.0
}

5. Compile and run!

Your section headers should now be properly sizing.


Notes:

  1. Make sure your data source is not random text that doesn't depend on the section index.

  2. If your section headers overlap or are improperly sized, make sure that your data source isn't changing while the UITableView is scrolling.

Dynamic section header height on runtime

When performing some action that changes the height of a cell (including header / footer cells), you have to inform the table view that the height has changed.

This is commonly done with either:

tableView.beginUpdates()
tableView.endUpdates()

or:

tableView.performBatchUpdates(_:completion:)

In this case, you want to call this when the text in your text view changes - easily done with a "callback" closure.

Here is an example of using a UITextView in a reusable UITableViewHeaderFooterView.

This will apply to loading a complex view from a XIB, but since this view is simple (only contains a UITextView), we'll do it all from code. This example uses 3 sections, each with 12 rows (default table view cells).

First, the table view controller class - no @IBOutlet or @IBAction connections, so just create a new UITableViewController and set its custom class to MyTestSectionHeaderTableViewController:

class MyTestSectionHeaderTableViewController: UITableViewController {

var myHeaderData: [String] = [
"Section 0",
"Section 1",
"Section 2",
]

override func viewDidLoad() {
super.viewDidLoad()

tableView.rowHeight = 50

tableView.keyboardDismissMode = .onDrag

tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 75

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "defCell")
tableView.register(MySectionHeaderView.self, forHeaderFooterViewReuseIdentifier: MySectionHeaderView.reuseIdentifier)
}

override func numberOfSections(in tableView: UITableView) -> Int {
return myHeaderData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 12
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "defCell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: MySectionHeaderView.reuseIdentifier) as! MySectionHeaderView

v.myTextView.text = myHeaderData[section]

v.textChangedCallback = { txt in
self.myHeaderData[section] = txt
tableView.performBatchUpdates(nil, completion: nil)
}

return v

}

}

and this is the UITableViewHeaderFooterView class. Note that it needs to conform to UITextViewDelegate so we can tell the controller the text has changed (so it can update the height when needed), and we pass back the newly edited text to update our data source:

class MySectionHeaderView: UITableViewHeaderFooterView, UITextViewDelegate {
static let reuseIdentifier: String = String(describing: self)

var myTextView: UITextView = {
let v = UITextView()
v.isScrollEnabled = false
return v
}()

var textChangedCallback: ((String) -> ())?

override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}

func commonInit() -> Void {

contentView.addSubview(myTextView)

myTextView.translatesAutoresizingMaskIntoConstraints = false

let g = contentView.layoutMarginsGuide

NSLayoutConstraint.activate([
myTextView.topAnchor.constraint(equalTo: g.topAnchor),
myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
myTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
myTextView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
])

myTextView.delegate = self
}

func textViewDidChange(_ textView: UITextView) {
guard let str = textView.text else {
return
}
textChangedCallback?(str)
}

}

The result:

Sample Image



Related Topics



Leave a reply



Submit