Nicolas Miari
Nicolas Miari

Reputation: 16246

tableView(_:viewforHeaderInSection:) Not Being Called

This one is driving me crazy.

  1. I have a view controller, subclass of UITableViewController.
  2. The table view's data source is replaced by a custom data source object, other than the view controller (by default, UITableViewController is both the delgate and the data source of its table view).
  3. The custom data source does NOT implement tableView(_:titleForHeaderInSection:)
  4. The view controller (the delegate) implements:

    • tableView(_:viewforHeaderInSection:)
    • tableView(_:heightForHeaderInSection:)
    • tableView(_:estimatedHeightForHeaderInSection)

    ...neither of which is ever called.

As a result, my table view sections do not display any header.

If I specify the table view's global section header height:

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.estimatedSectionHeaderHeight = 12 // (say)

...then blank headers are displayed, but tableView(_:titleForHeaderInSection:) et al are still not called and my custom header views never get displayed.

Similar questions have been asked before, and Apple's docs seems to not be 100% correct, however I am positive that I am doing everything that is required (I have tried all configurations).

What am I missing?

The codebase is large, and not mine; perhaps I am missing something somwehere but don't know what else to look for...


Update

I created a new, minimal project to test and it turns out I only need to implement these two delegate methods (and not modify any properties of the table view):

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) // (say)
    view.backgroundColor = UIColor.red // (say)
    return view
}

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

...to have my custom section header view displayed.

However, the same setup does not work on my existing project. Something, somewhere must be interfering...


Update 2

I sublcassed UITableView to see what was going on, but still can't figure it out (see inline comments):

import UIKit

class DebugTableView: UITableView {

    // GETS EXECUTED:
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder) 
    }

    override var estimatedSectionHeaderHeight: CGFloat {
        // NEVER GETS EXECUTED:
        get { 
            return super.estimatedSectionHeaderHeight
        }
        // NEVER GETS EXECUTED:
        set (newValue) { 
            super.estimatedSectionHeaderHeight = newValue
        }
    }

    override var sectionHeaderHeight: CGFloat {
        // NEVER GETS EXECUTED:
        get { 
            return super.sectionHeaderHeight
        }
        // NEVER GETS EXECUTED:
        set (newValue) { 
            super.sectionHeaderHeight = newValue
        }
    }

    // NEVER GETS EXECUTED:
    override func headerView(forSection section: Int) -> UITableViewHeaderFooterView? {
        let view = super.headerView(forSection: section)
        return view
    }

    // GETS EXECUTED ONCE PER CELL:            
    override func rectForHeader(inSection section: Int) -> CGRect {
        var rect = super.rectForHeader(inSection: section)
        rect.size.height = 1000 // HAS NO EFFECT
        return rect
    }
} 

Upvotes: 1

Views: 2580

Answers (2)

Ketan Parmar
Ketan Parmar

Reputation: 27428

You just need to set sectionHeaderHeight also, not only estimated height!

like,

Swift 4.0

    tableView.sectionHeaderHeight = UITableView.automaticDimension
    tableView.estimatedSectionHeaderHeight = 25

and then just override viewForHeaderInSection. and no need to override any other delegates!

Upvotes: 2

Nicolas Miari
Nicolas Miari

Reputation: 16246

I found the problem.

It turns out there was an auxiliary object overtaking the role of table view delegate and hiding it.

This class:

  • Adopts the UITableViewDelegate protocol and implements most of its methods (but not the header/footer-related ones),
  • Sets itself as the table view's delegate inside the table view controller's viewDidAppear() method,
  • Keeps a reference to the table view controller,
  • When it's implementation of (say) tableView(_:didSelectRowAtIndexPath:) is called, it in turn calls the table view controller's equivalent method, giving the impression that the view controller is still the delegate:

    class ManInTheMiddle: NSObject, UITableViewDelegate {
        var formerDelegate: UITableViewController?
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            formerDelegate?.tableView(tableView, didSelectRowAt: indexPath)
            // Do some other stuff...
        }
    }
    

This is why some methods of UITableViewDelegate where being called on my table view controller and some weren't.

I discovered this by overriding the table view's delegate property:

class DebugTableView: UITableView {

    // ...
    override var delegate: UITableViewDelegate? {
        get {
            return super.delegate
        }
        set (newValue) {
            super.delegate = newValue // < BREAKPOINT HERE
        }
    }

...out of desperation (there was no strong reason to suspect the delegate was swapped, being that most methods worked...)

<rant>Damn, do I hate these kind of dirty hacks... This is almost #define true false territory, sheesh... </rant>

Upvotes: 1

Related Questions