Setting tableView header's height in Swift

Here is a solution which uses section header views rather than the actual table header view:


If you'd like to use a header for you UITableView instead you can design another prototype cell in Interface Builder, make a custom class based on a UITableViewCell and assign it to the prototype cell in interface builder on the class inspector.

Then in your controller you're going to use

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

In that function you're actually going to create a reusable cell from your table view but cast as the custom cell you made for the header. You will have access to all of it's properties like a regular UITableViewCell, then you're just going to return the cell's view

return cell.contentView

Another method you're going to use is

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

That one is pretty self explanatory.

Swift 3.0.1

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

Swift 3/Xcode 8:

Add this in viewDidLoad():

let HEADER_HEIGHT = 100
tableView.tableHeaderView?.frame.size = CGSize(width: tableView.frame.width, height: CGFloat(HEADER_HEIGHT))

Enjoy!


The accepted answer doesn't actually answer the question. It instead offers an alternative by using the SECTION header. This question has been answered by others but I will duplicate the answer here with a few more instructions.


Loading the view

Table views are as old as iPhones and therefore you sometimes have to force it to do what you want.

First we need to load the header and manually set its height. Otherwise the view will take more height than it needs. We do this on the viewDidLayoutSubviews callback:

lazy var profileHeaderView: ProfileHeaderView = {
    let headerView = ProfileHeaderView()
    return headerView
}()

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    sizeHeaderToFit()
}

private func sizeHeaderToFit() {
    profileHeaderView.setNeedsLayout()
    profileHeaderView.layoutIfNeeded()

    var frame = profileHeaderView.frame
    frame.size.height = profileHeaderView.calculateHeight()
    profileHeaderView.frame = frame

    tableView.tableHeaderView = profileHeaderView
}

As you can see, I like to put my views inside lazy vars. This ensures that they are always created but only when I start using them.

You can also see that I'm calculating the height. In some cases, your height is fixed and therefore you can just set the frame height to a hardcoded value.


Set some priorities

We will likely see some constraint warnings appear in our debugger. This happens because the table view first forces a 0x0 size before using the size we specified above At this moment, your constraints and the height of the view are in conflict with each other.

To clear these, we simply set the constraint priorities. First you should wrap your header view components inside another view (I generally always do this for header views). This will make managing constraints much easier on your header view.

We then need to set the bottom constraint priorities to high:

containerView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
containerView.setContentHuggingPriority(.defaultHigh, for: .vertical)

Here is a more complete example:

WARNING: Thought it is still useful as a guide for laying out your views, do not use this code if you're creating your views using nibs or storyboards.

class ProfileHeaderView: UIView {
    lazy var containerView: UIView = {
        let view = UIView()
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLayout()
    }

    required init?(coder aDecoder: NSCoder) {
        // We do this because the view is not created using storyboards or nibs.
        fatalError("init(coder:) has not been implemented")
    }

    private func setupLayout() {
        self.addSubview(containerView)

        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        containerView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
        containerView.setContentHuggingPriority(.defaultHigh, for: .vertical)

        // Set the rest of your constraints against your containerView not self and add your subviews to your containerView not self
    }
}

Here is the example of the constraints set using snap-kit:

containerView.snp.makeConstraints() { make in
    make.top.equalTo(self.snp.top)
    make.leading.equalTo(self.snp.leading)
    make.trailing.equalTo(self.snp.trailing)
    make.bottom.equalTo(self.snp.bottom).priority(.high)
}

Make sure you add your constraints to the containerView not self and use containerView to add your subviews and rest of your constraints.