How to hide first section header in UITableView (grouped style)
Asked Answered
E

16

117

As the design of table views using the grouped style changed considerably with iOS 7, I would like to hide (or remove) the first section header. So far I haven't managed to achieve it.

Somewhat simplified, my code looks like this:

- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    if (section == 0)
        return 0.0f;
    return 32.0f;
}

- (UIView*) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    if (section == 0) {
        UIView* view = [[UIView alloc] initWithFrame: CGRectMake(0.0f, 0.0f, 640.0f, 0.0f)];
        return view;
    }
    return nil;
}

- (NSString*) tableView:(UITableView *) tableView titleForHeaderInSection:(NSInteger)section
{
    if (section == 0) {
        return nil;
    } else {
        // return some string here ...
    }
}

If I return a height of 0, the other two methods will never be called with the section index 0. Yet an empty section header is still drawn with the default height. (In iOS 6, the two methods are called. However, the visible result is the same.)

If I return a different value, the section header gets the specified height.

If I return 0.01, it's almost correct. However, when I turn on "Color Misaligned Images" in the simulator, it marks all table view cells (which seems to be a logical consequence).

The answers to the question UITableView: hide header from empty section seem to indicate that some people were successful in hiding the section header. But it might apply to the plain style (instead of the grouped one).

The best compromise so far is returning the height 0.5, resulting in a somewhat thicker line below the navigation bar. However, I'd appreciate if somebody knows how the first section header can be completely hidden.

Update

According to caglar's analysis (https://mcmap.net/q/187124/-how-to-hide-first-section-header-in-uitableview-grouped-style), the problem only arises if the table view is contained in a navigation controller.

Eden answered 27/9, 2013 at 17:9 Comment(3)
I didn't get that part => if(section==0) return view; return nil; i.e. returning a view when its the first section and nil otherwise?Bedchamber
The idea is to return a view with a height of 0 for the first section and return nil for all other sections so that the table view uses the default header view for them. The nil part nicely works; the table view shows a header for these sections. But the part for section 0 is irrelevant because the method is never called with section == 0.Eden
This answer seems to be short and sweet. https://mcmap.net/q/189144/-how-to-change-height-of-grouped-uitableview-headerNonreturnable
E
160

I have a workaround that seems reasonably clean to me. So I'm answering my own question.

Since 0 as the first section header's height doesn't work, I return 1. Then I use the contentInset to hide that height underneath the navigation bar.

Objective-C:

- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    if (section == 0)
            return 1.0f;
    return 32.0f;
}

- (NSString*) tableView:(UITableView *) tableView titleForHeaderInSection:(NSInteger)section
{
    if (section == 0) {
        return nil;
    } else {
        // return some string here ...
    }
}

- (void) viewDidLoad
{
    [super viewDidLoad];

     self.tableView.contentInset = UIEdgeInsetsMake(-1.0f, 0.0f, 0.0f, 0.0);
}

Swift:

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return section == 0 ? 1.0 : 32
}

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.contentInset = UIEdgeInsets(top: -1, left: 0, bottom: 0, right: 0)
}
Eden answered 27/9, 2013 at 19:41 Comment(10)
Using a header height of 0.1f is even better for hiding it.Inlaid
I've tried using 0.1f. But the result is that drawings are no longer aligned to pixels, causing blurs and reducing performance. Just try it in the simulator: it has an option to highlight problematic alignment.Eden
Actually, a better implementation (less likely to break in the future) is to use CGFLOAT_MIN instead of a hard-coded value. In other words, don't return 1.0f or 0.1f instead, return CGFLOAT_MIN If Apple ever changes the minimum acceptable value, you'll have code to change if you hard-code the return value. Also, you already don't know if you're using the smallest value possible. Using the defined constant, you're guaranteed to be using the smallest value possible and your code will survive OS changes.Sectary
@Chris: I'm afraid you didn't get the idea. I don't return a very small value but a multiple of the size of a pixel so that drawing remains pixel aligned (for good performance and crispness). I then offset it using the content inset. This is unlikely to change in the future as it is a considerable, visible distance and not something the OS could ignore.Eden
+1 For not using fractional pixel values as UI offsets.Reform
use this code to return the current title (in place of "return some string here ..." above): return [super tableView:self.tableView titleForHeaderInSection:section];Beira
@ChrisOstmo actually it changed, now is CGFloat.minAlphaalphabet
@JuanPabloBoeroAlvarez CGFLOAT_MIN is an "informational macro" and according to Apple's documentation, it is still there. I can't find any reference anywhere to "CGFloat.min," however, I can find this: developer.apple.com/library/mac/documentation/GraphicsImaging/…Sectary
It's not enough, you also need to implement for footer like that. Work with iOS 10, Xcode 8.3.3Garotte
Now CGFLOAT_MIN is renamed to CGFloat.leastNormalMagnitude.Calendula
R
58

This is how to hide the first section header in UITableView (grouped style).

Swift 3.0 & Xcode 8.0 Solution

  1. The TableView's delegate should implement the heightForHeaderInSection method

  2. Within the heightForHeaderInSection method, return the least positive number. (not zero!)

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    
        let headerHeight: CGFloat
    
        switch section {
        case 0:
            // hide the header
            headerHeight = CGFloat.leastNonzeroMagnitude
        default:
            headerHeight = 21
        }
    
        return headerHeight
    }
    
Recalescence answered 5/12, 2016 at 12:41 Comment(4)
returning CGFloat.leastNonzeroMagnitude instead of 0 helped me. Thanks!Fipple
Perfect answer, ThanksHypogeum
By the way, CGFLOAT_MIN for objc is identical to CGFloat.leastNonzeroMagnitude in SwiftBalsamic
What if I want the contents of the other headers to define the header size instead of a static/hard coded 21?Moldy
H
36

The answer was very funny for me and my team, and worked like a charm

  • In the Interface Builder, Just move the tableview under another view in the view hierarchy.

REASON:

We observed that this happens only for the First View in the View Hierarchy, if this first view is a UITableView. So, all other similar UITableViews do not have this annoying section, except the first. We Tried moving the UITableView out of the first place in the view hierarchy, and everything was working as expected.

Hive answered 11/12, 2013 at 11:27 Comment(5)
Do you still have the frosted glass effect of the navigation bar when you scroll the table view?Eden
You will have this blur effect when you setup your tableView accordingly. You'd need to set the contentInset to for example {0, 64, 0, 0} to have the 64px offset from top (status bar plus navigation bar). The the tableView needs to be attached at the screen top, not the topLayoutGuide (to let it reach under the nav bar)Wonacott
If you are not using pull to refresh this would look fine.Uruguay
Wow. This worked for me in iOS 9 and just blew my mind.Piccoloist
This should be the accepted answer. Such a weird bug!Verdun
E
26

Use this trick for grouped type tableView

Copy paste below code for your table view in viewDidLoad method:

tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, tableView.bounds.size.width, 0.01f)];
Eocene answered 30/4, 2015 at 13:47 Comment(5)
I haven't tried it but I suspect this solution has the same disadvantage as some of the other proposed solutions: drawings are no longer aligned to pixels, causing blurs and reducing performance. The simulator can reveal it: it has an option to highlight problematic alignment. However, if it does round down to 0, then it's a nice solution indeed.Eden
Yes, I guess it does round down to 0. Because I used it many times, but didn't find any blur in drawings.Eocene
TableHeaderView and Section Header view? what is the difference,Amritsar
Or slightly shorter: _tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 1)].Abbacy
@AsifHabib: tableHeaderView is place at top of all table elements. It's header view for table itself. And section header view is view to be placed on each section. There can be many section header views according to specified no of sections in table. But, there will be only one table header view.Eocene
E
22

this way is OK.

override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    if section == 0 {
        return CGFloat.min
    }
    return 25
}

override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if section == 0 {
        return nil
    }else {
        ...
    }
}
Eckhart answered 31/3, 2015 at 2:23 Comment(3)
This is a nice variation of my solution except it uses fractional values. That way, text and images are not longer aligned to pixels, drawing is slowed down and the result can become blurry. The simulator has the option "Color misaligned images" to reveal this problem.Eden
Works just fine for people like me using static cells with a containerViewThermostat
Swift 3 > 'CGFloat.min' has been renamed to 'CGFloat.leastNormalMagnitude'Fredi
C
6

I just copied your code and tried. It runs normally (tried in simulator). I attached result view. You want such view, right? Or I misunderstood your problem?

enter image description here

Conto answered 27/9, 2013 at 17:36 Comment(6)
Yes, that's basically what I want, except my table view is in a navigation controller. Could that make the difference?Eden
I added tableView to navigation controller and your situation appears:) I think in iOS 7, this header height fix by default in grouped style table view. By changing frame of tableView, first header view may be hidden.(like set x = 0 y = -40). However, I know this solution is not a good solution.Conto
Thank you very much for your work. So it seems that the problem only arises if the table view uses the grouped styled and is contained within a navigation controller.Eden
Trying to change from viewcontroller to tableviewcontroller I couldn't fix the issue with the header. Maybe I am missing something trying to convert it. (iOS 7.0.3 sim)Etz
This issue appears in this snapshot as well! the 0th cell is not starting from x:0.0f & y:0.0f.Valuate
For other struggling with this issue in iOS 10... I was able to fix this by setting self.chooserTable.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.chooserTable.frame.size.width, CGFLOAT_MIN)]. This workaround was required for my grouped style tableViews inside viewControllers with NavigationViewControllers.Sovran
D
6

Swift3 : heightForHeaderInSection works with 0, you just have to make sure header is set to clipsToBounds.

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

if you don't set clipsToBounds hidden header will be visible when scrolling.

func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
    guard let header = view as? UITableViewHeaderFooterView else { return }

    header.clipsToBounds = true
}
Dolores answered 30/7, 2017 at 13:6 Comment(1)
clipsToBound = true is very important if you plan to completely hide the section header view. Setting its height to 0/CGFLOAT_MIN is not enough.Faithfaithful
S
6

Update[9/19/17]: Old answer doesn't work for me anymore in iOS 11. Thanks Apple. The following did:

self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionHeaderHeight = 20.0f;
self.tableView.contentInset = UIEdgeInsetsMake(-18.0, 0.0f, 0.0f, 0.0);

Previous Answer:

As posted in the comments by Chris Ostomo the following worked for me:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return CGFLOAT_MIN; // to get rid of empty section header
}
Sihunn answered 14/9, 2017 at 22:7 Comment(3)
I suspect that the content of your table view is no longer aligned to pixels, causing blurs and reducing performance. The simulator can reveal it: it has an option to highlight problematic alignment. However, if it does round down to 0, then it's a nice solution indeed.Eden
No, I didn't notice any content issues. Constraints seemed fine.Sihunn
Welp! Doesn't work in iOS 11 anymore! My fix lasted less than a week.. 😞Sihunn
L
5

Here is how to get rid of the top section header in a grouped UITableView, in Swift:

tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
Lithotomy answered 2/7, 2018 at 16:42 Comment(0)
A
4

Try this if you want to remove all section header completely

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

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return CGFloat.leastNormalMagnitude
}
Affecting answered 22/5, 2019 at 7:19 Comment(1)
That is just I need. A simple and easy solution. 1+Ethelstan
G
3

In Swift 4.2 and many earlier versions, instead of setting the first header's height to 0 like in the other answers, you can just set the other headers to nil. Say you have two sections and only want the second one (i.e., 1) to have a header. That header will have the text Foobar:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return section == 1 ? "Foobar" : nil
}
Gourmandise answered 1/8, 2018 at 1:55 Comment(0)
A
2

In iOS 15, this code removes the unwanted section header space.

 if #available(iOS 15.0, *) {
  tableView.sectionHeaderTopPadding = .leastNormalMagnitude
}
Adelaadelaida answered 22/10, 2021 at 0:23 Comment(0)
B
0

I can't comment yet but thought I'd add that if you have a UISearchController on your controller with UISearchBar as your tableHeaderView, setting the height of the first section as 0 in heightForHeaderInSection does indeed work.

I use self.tableView.contentOffset = CGPointMake(0, self.searchController.searchBar.frame.size.height); so that the search bar is hidden by default.

Result is that there is no header for the first section, and scrolling down will show the search bar right above the first row.

Bowline answered 15/10, 2015 at 22:25 Comment(0)
K
0

easiest by far is to return nil, or "" in func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? for a section where you do not wish to display header.

Kordofan answered 7/8, 2017 at 15:14 Comment(0)
T
0

Swift Version: Swift 5.1
Mostly, you can set height in tableView delegate like this:

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
   return UIView(frame: CGRect(x: 0, y: 0, width: view.width, height: CGFloat.leastNormalMagnitude))
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
   return CGFloat.leastNormalMagnitude
}

Sometimes when you created UITableView with Xib or Storyboard, the answer of up does not work. you can try the second solution:

let headerView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
self.tableView.tableHeaderView = headerView

Hope it works for you!

Tigre answered 24/3, 2020 at 6:1 Comment(0)
N
0

The following worked for me in with iOS 13.6 and Xcode 11.6 with a UITableViewController that was embedded in a UINavigationController:

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

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

No other trickery needed. The override keywords aren't needed when not using a UITableViewController (i.e. when just implemented the UITableViewDelegate methods). Of course if the goal was to hide just the first section's header, then this logic could be wrapped in a conditional as such:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    if section == 0 {
        return nil
    } else {
        // Return some other view...
    }
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    if section == 0 {
        return .zero
    } else {
        // Return some other height...
    }
}
Nonpartisan answered 18/8, 2020 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.