Swift – How creating custom viewForHeaderInSection, Using a XIB file?

The typical process for NIB based headers would be:

  1. Create UITableViewHeaderFooterView subclass with, at the least, an outlet for your label. You might want to also give it some identifier by which you can reverse engineer to which section this header corresponds. Likewise, you may want to specify a protocol by which the header can inform the view controller of events (like the tapping of the button). Thus, in Swift 3 and later:

    // if you want your header to be able to inform view controller of key events, create protocol
    
    protocol CustomHeaderDelegate: class {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int)
    }
    
    // define CustomHeader class with necessary `delegate`, `@IBOutlet` and `@IBAction`:
    
    class CustomHeader: UITableViewHeaderFooterView {
        static let reuseIdentifier = "CustomHeader"
    
        weak var delegate: CustomHeaderDelegate?
    
        @IBOutlet weak var customLabel: UILabel!
    
        var sectionNumber: Int!  // you don't have to do this, but it can be useful to have reference back to the section number so that when you tap on a button, you know which section you came from; obviously this is problematic if you insert/delete sections after the table is loaded; always reload in that case
    
        @IBAction func didTapButton(_ sender: AnyObject) {
            delegate?.customHeader(self, didTapButtonInSection: section)
        }
    
    }
    
  2. Create NIB. Personally, I give the NIB the same name as the base class to simplify management of my files in my project and avoid confusion. Anyway, the key steps include:

    • Create view NIB, or if you started with an empty NIB, add view to the NIB;

    • Set the base class of the view to be whatever your UITableViewHeaderFooterView subclass was (in my example, CustomHeader);

    • Add your controls and constraints in IB;

    • Hook up @IBOutlet references to outlets in your Swift code;

    • Hook up the button to the @IBAction; and

    • For the root view in the NIB, make sure to set the background color to “default” or else you’ll get annoying warnings about changing background colors.

  3. In the viewDidLoad in the view controller, register the NIB. In Swift 3 and later:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
    }
    
  4. In viewForHeaderInSection, dequeue a reusable view using the same identifier you specified in the prior step. Having done that, you can now use your outlet, you don’t have to do anything with programmatically created constraints, etc. The only think you need to do (for the protocol for the button to work) is to specify its delegate. For example, in Swift 3:

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomHeader") as! CustomHeader
    
        headerView.customLabel.text = content[section].name  // set this however is appropriate for your app's model
        headerView.sectionNumber = section
        headerView.delegate = self
    
        return headerView
    }
    
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44  // or whatever
    }
    
  5. Obviously, if you’re going to specify the view controller as the delegate for the button in the header view, you have to conform to that protocol:

    extension ViewController: CustomHeaderDelegate {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
            print("did tap button", section)
        }
    }
    

This all sounds confusing when I list all the steps involved, but it’s really quite simple once you’ve done it once or twice. I think it’s simpler than building the header view programmatically.


In matt’s answer, he protests:

The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector.

This is simply not correct. If you use the above NIB-based approach, the class that is instantiated for the root view of this header view is a UITableViewHeaderFooterView subclass, not a UIView. It instantiates whatever class you specify for the base class for the NIBs root view.

What is correct, though, is that some of the properties for this class (notably the contentView) aren’t used in this NIB based approach. It really should be optional property, just like textLabel and detailTextLabel are (or, better, they should add proper support for UITableViewHeaderFooterView in IB). I agree that this is poor design on Apple’s part, but it strikes me as a sloppy, idiosyncratic detail, but a minor issue given all the problems in table views. E.g., it is extraordinary that after all these years, that we still can’t do prototype header/footer views in storyboards at all and have to rely on these NIB and class registration techniques at all.

But, it is incorrect to conclude that one cannot use register(_:forHeaderFooterViewReuseIdentifier:), an API method that has actively been in use since iOS 6. Let’s not throw the baby out with the bath water.


See previous revision of this answer for Swift 2 renditions.

Leave a Comment