How can I hide section headers in iOS 11?

Viewed 7710

In iOS 11, my section headers always appear, regardless of whether the items are 0 or more.

On iOS 10 devices, my code works and sections disappear when item count is 0. But on iOS 11, the same code has no affect.

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if sections[section].items.count > 0{
        return sections[section].title
    }else{
        return nil
    }
}
8 Answers

In iOS 11 if you implement only titleForHeaderInSection and return nil, you will not see a header view. But if you also implement viewForHeaderInSection, regardless of what you return, there will be a section.

This alone will not show a section header:

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return nil
}

This will show a section header with no title:

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return nil
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return nil
}

So both methods may return nil and the header will be visible. If only titleForHeaderInSection is implemented, no header shows up. That does seem to be a case only in iOS 11. Not sure if it's a bug or a way to force developers chose one method of the two. But the docs confirm this behaviour about titleForHeaderInSection:

"Return Value: A string to use as the title of the section header. If you return nil , the section will have no title."

So nothing about showing or not showing, this method only returns the string for the title. Which makes sense. But what does look like a bug is that returning nil in viewForHeaderInSection will show the section header.

To hide a section header for, say, section 0, implement the following method like so:

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    if (section == 0) {
        return CGFloat.leastNormalMagnitude //Now section 0's header is hidden regardless of the new behaviour in iOS11.
    }

    return tableView.sectionHeaderHeight
}

This solution also works for grouped UITableViews, as discussed below.

Update: If you execute reloadSections(..), this solution causes an

NSException of type 'CALayerInvalidGeometry'

If you return 0 in the if statement however, this crash doesn't occur! :)

Therefore, I would say the best solution I have found (atleast for plain UITableViews) is:

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    if (section == 0) {
        return 0
    }

    return tableView.sectionHeaderHeight
}

Implement tableView(_:heightForHeaderInSection:) to return UITableViewAutomaticDimension.

This will suppress the section header in exactly the case where titleForHeaderInSection returns nil (and otherwise it will use the default header height from the table).

If you explicitly tell iOS 11 to use a height of 0 in heightForHeaderInSection it will hide the section header. You can still use automatic header sizing by returning UITableViewAutomaticDimension for non-zero height headers. Here's an example of a solution to workaround the iOS 11 bug:

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

You'll need to implement shouldShowSectionHeader to determine whether or not to show the section header.

return empty view from viewForHeaderInSection

swift 3, 4 and 4.2

To hide your tableView header

tableView.tableHeaderView?.frame = CGRect.zero

and to show it back

tableView.tableHeaderView?.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 66)

What worked for me:

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