130

New in iOS 8, you can obtain 100% dynamic table view cells by simply setting the estimated row height, then layout your elements in the cell using Auto Layout. If the content increases in height, the cell will also increase in height. This is extremely useful, and am wondering if the same feat can be accomplished for section headers in a table view?

Can one, for example, create a UIView in tableView:viewForHeaderInSection:, add a UILabel subview, specify auto layout constraints for the label against the view, and have the view increase in height to fit the label's contents, without having to implement tableView:heightForHeaderInSection:?

The documentation for viewForHeaderInSection states: "This method only works correctly when tableView:heightForHeaderInSection: is also implemented." I haven't heard if anything has changed for iOS 8.

If one cannot do that, what is the best way to mimic this behavior?

Jordan H
  • 52,571
  • 37
  • 201
  • 351

10 Answers10

303

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
}
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • 4
    That's cool if it works, but the docs don't mention it, and neither did the WWDC session if I recall correctly. The docs for `UITableViewAutomaticDimension` say "if you return this constant in `tableView:heightForHeaderInSection:` or `tableView:heightForFooterInSection:`, `UITableView` uses a height that fits the value returned from `tableView:titleForHeaderInSection:` or `tableView:titleForFooterInSection:` (if the title is not `nil`)." – Aaron Brager Apr 21 '15 at 05:03
  • 1
    I can confirm it works beautifully for auto layout constraints I've set up, including additional padding added - it's not just the height of the text. In fact I did not implement `tableView:titleForHeaderInSection:`, there's no need to. – Jordan H Apr 21 '15 at 05:06
  • 1
    It doesn't seem to work for me when I *don't* explicitly specify `sectionHeaderHeight`, but setting it to `UITableViewAutomaticDimension` as you did in your example works fine. (Which makes sense, because `sectionHeaderHeight` seems to default to `22.0`.) – Sasha Chedygov Jun 25 '15 at 23:46
  • 32
    This didn't work for me. My section header was able to calculate the height automatically, but it overlaps the cells in the section. I'm setting up my header view using a xib file and setting my constraints in interface builder. Is that my problem? Where are you guys creating the constraints? Thanks! – CoBrA2168 Jul 21 '15 at 20:56
  • Not working for me, don't know what can differ from your project. I have started a new one from scratch with just the basic stuff but no success... – PakitoV Sep 29 '15 at 14:04
  • 2
    I got it working finally!! Key factor for me: I was loading the header views from a xib. Do NOT use a UITableViewCell as base in the IB, use a plain UIView and set the constrains there. Its working as expected, dynamically growing according to the label text length and so on, will upload a sample code later on. – PakitoV Sep 29 '15 at 14:46
  • 5
    As promised here is the sample code: https://github.com/ebetabox/DynamicCellSectionHeight – PakitoV Sep 29 '15 at 16:34
  • 2
    It appears that if using `estimatedSectionHeaderHeight`, you also HAVE to use `estimatedHeight`. This single change fixed the section header overlapping on my cells. – Nerkatel Nov 03 '15 at 15:35
  • @Emilio I get `DynamicSectionHeaderHeight[75271:829056] Unable to simultaneously satisfy constraints.` error. Do you see it too? – netwire Nov 07 '15 at 16:38
  • @Emilio I figured it out. The estimatedHeight needs to match the initial height in the XIB (using Freeform for example) – netwire Nov 07 '15 at 16:51
  • 10
    Another key point here, your table cells need to also be configured for auto height (UITableViewAutomaticDimension) calculation for it to work on the sections. – Bob Spryn Dec 04 '15 at 22:47
  • 10
    You must have `estimatedSectionHeaderHeight` or `tableView:estimatedHeightForHeaderInSection:` AND `sectionHeaderHeight` or `tableView:heightForHeaderInSection:`. Additionally, if you're using the delegate methods, you must return a value larger than 1.00 for the estimate (completely undocumented behavior), otherwise the delegate for height will NOT be called and the default table view header height will be used. – Vadoff Apr 28 '16 at 06:20
  • hi, previously i was setting estimatedsectinheight = 400 but, the problem is my app crashed but when i made it equal to 25 it worked fine? whats the logic. – Nex Mishra Jul 13 '16 at 20:35
  • This solution is making my section header take up way too much vertical space. All I am doing is setting the title, nothing special. Any suggestions? – Alexander Oct 03 '16 at 19:15
  • Regarding my comment above, I managed to work around it by creating a `UILabel` and setting its properties equal to the `UITableViewHeaderFooterView`'s `textLabel`. Then I set my `UILabel`'s constraints in the `UITableView`'s `willDisplayHeaderView` delegate method. – Alexander Oct 03 '16 at 19:37
  • This solution works fine if you are installing from xcode since heights are calculated during debug. If you try to install via AdHoc this method of implementation WILL crash. – random Oct 06 '16 at 20:25
  • My TableHeader view contains collection view which has dynamic labels. Automatic height for header not works now. Help please. – pkc456 Feb 09 '17 at 11:15
  • 1
    If you want to keep other sections at zero height, add the aforementioned properties, then implement the delegate method `tableView(_ tableView: UITableView, heightForHeaderInSection section: Int)` and return zero. For dynamic sections, return `UITableViewAutomaticDimension`. – MkVal Mar 26 '17 at 08:02
  • 3
    @Joey i am also using same code for dynamic header but its not working in iOS9 in IOS 10 it works perfect did u have any idea about that – Sunil M. Apr 27 '17 at 13:45
  • @iOSDev, did you find a solution for ios9? – AndreiS May 24 '17 at 11:52
  • @Emilio, Try implementing header collapsed and uncollapsed and you will see the replications of the header (it will take some time..) – OhadM Jun 19 '17 at 19:28
  • Yes, after keeping automatic values, make sure you don't implement the `heightForHeaderInSection` delegate method if all your section headers should resize automatically. – Rakshitha Muranga Rodrigo Jan 29 '22 at 17:28
  • Thanks! I had to add `headerLabel.numberOfLines = 0` to let the label expand on longer text. – Mario Huizinga Oct 15 '22 at 12:58
35

This can be accomplished by setting (or returning) the estimatedSectionHeaderHeight on your table view.

If your section header is overlapping your cells after setting estimatedSectionHeaderHeight, make sure that you're using an estimatedRowHeight as well.

(I'm adding this answer because the second paragraph contains an answer to an issue that can be found after reading through all of the comments which some might miss.)

Clay Ellis
  • 4,960
  • 2
  • 37
  • 45
25

Got stuck in the same issue where header was getting zero height untill and unless I provide a fixed height in the delegate for heighForHeaderInSection.

Tried a lot of solutions which includes

self.tableView.sectionHeaderHeight = UITableView.automaticDimension
self.tableView.estimatedSectionHeaderHeight = 73

But nothing worked. My cell were using proper autolayouts too. Rows were changing their height dynamically by using the following code but section header weren't.

self.tableView.estimatedRowHeight = 135
self.tableView.rowHeight = UITableView.automaticDimension

The fix is extremely simple and weird too but I had to implement the delegate methods instead of 1 line code for the estimatedSectionHeaderHeight and sectionHeaderHeight which goes as follows for my case.

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
    return 73
}
Maulik Pandya
  • 2,200
  • 17
  • 26
Ahsan Ebrahim Khatri
  • 1,807
  • 1
  • 20
  • 27
22

Swift 4+

working(Tested 100%)

If you need both section as well row with dynamic height based on content then you can use below code:

On viewDidLoad() write this lines:

    self.globalTableView.estimatedRowHeight = 20
    self.globalTableView.rowHeight = UITableView.automaticDimension

    self.globalTableView.sectionHeaderHeight =  UITableView.automaticDimension
    self.globalTableView.estimatedSectionHeaderHeight = 25;

Now we have set row height and section height by using UITableView Delegate methods:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
   {
       return UITableView.automaticDimension
   }
   func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

       return UITableView.automaticDimension

   }
Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52
  • **NOTE** to this great answer. *As of 2020* it seems to be ok to ONLY use the four properties in viewDidLoad. – Fattie Jun 11 '20 at 22:38
  • **NOTE** to this great answer. *As of 2020* you DO need the two "useless" lines giving a estimated height (say, 20 or so). – Fattie Jun 11 '20 at 22:44
9

I tried

self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionHeaderHeight = 25;

but it didn't size correctly header with multiline label. Added this to solve my problem:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Recalculates height
    tableView.beginUpdates()
    tableView.endUpdates()
}
iuriimoz
  • 951
  • 10
  • 11
  • do define the data source functions ``tableView:estimatedHeightForHeaderInSection`` and ``tableView:heightForHeaderInSection`` instead of defining at viewDidLoad. – Keith Yeoh Feb 08 '18 at 06:10
  • I found setting `estimatedSectionHeaderHeight` is only necessary below iOS 11 – doctorBroctor Feb 05 '19 at 22:01
  • This is a great solution. If anyone is facing the 'Height is ambiguous' error, using tableView.reloadData() instead of .beginUpdates() and endUpdates() fixes it. – CristianMoisei Sep 12 '20 at 21:13
  • Still not working for me. I've a label & textfield in header view. – SNarula Oct 22 '21 at 05:53
6

In my case:

  1. set programmatically not work.

self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension.

  1. set in storyboard not work.

  2. override heightForHeaderInSection worked.

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return UITableViewAutomaticDimension
}

test enviroment:

  • Mac OS 10.13.4
  • XCode Version 9.4.1
  • Simulator iPhone 8 Plus
Codus
  • 1,433
  • 1
  • 14
  • 18
1

Yes, it works for me. I have to make more changes as below:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel()

    label.numberOfLines = 0
    label.text          = my own text

    return label
}
Matheus Lacerda
  • 5,983
  • 11
  • 29
  • 45
arg_vn
  • 19
  • 3
0

Swift 5, iOS 12+

The only way it worked for me was

tableView.sectionHeaderHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 40
tableView.estimatedRowHeight = 80

Then setting constraints for custom header view elements so that auto layout engine could determine their x,y position and row height. i.e

NSLayoutConstraint.activate([
    titleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
    titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
    titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24)
])
belotserkovtsev
  • 351
  • 2
  • 10
0

One more variant

  1. estimatedHeightForFooterInSection
public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
    return UITableView.automaticDimension
}
  1. heightForFooterInSection
public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return UITableView.automaticDimension
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
-1

I modified iuriimoz answer. Just replaced viewWillAppear method:

tableView.sectionHeaderHeight = UITableViewAutomaticDimension
tableView.estimatedSectionHeaderHeight = 25


override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Recalculates height
    tableView.layoutIfNeeded() 
}

Also add the tableView.layoutIfNeeded() to

override func viewDidAppear(_ animated: Bool) {

    super.viewDidAppear(animated)
    tableView.layoutIfNeeded()
}

For iOS 10

tableView.beginUpdates()
tableView.endUpdates()

have "fade" animation effect on viewWillAppear for me

Irina
  • 177
  • 2
  • 4