UITableView: hide header from empty section
Asked Answered
P

9

81

i have a UITableView, that displays expenses from a current month (see screenshot):

My problem is with the header for empty sections. is there any way to hide them? The data is loaded from coredata.

this is the code that generates the header title:

TitleForHeader

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
    return nil;
} else {

NSDate *today = [NSDate date ];
int todayInt = [dataHandler getDayNumber:today].intValue;

NSDate *date = [NSDate dateWithTimeIntervalSinceNow:(-(todayInt-section-1)*60*60*24)];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale preferredLanguages] objectAtIndex:0]]];    
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSString *formattedDateString = [dateFormatter stringFromDate:date];
    return formattedDateString;}

}

ViewForHeader

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
    return nil;
} else {

    UIView *headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 312, 30)];
    UILabel *title = [[UILabel alloc]initWithFrame:CGRectMake(4, 9, 312, 20)];
    UIView *top = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 312, 5)];
    UIView *bottom = [[UIView alloc]initWithFrame:CGRectMake(0, 5, 312, 1)];

    [top setBackgroundColor:[UIColor lightGrayColor]];
    [bottom setBackgroundColor:[UIColor lightGrayColor]];

    [title setText:[expenseTable.dataSource tableView:tableView titleForHeaderInSection:section]];
    [title setTextColor:[UIColor darkGrayColor]];
    UIFont *fontName = [UIFont fontWithName:@"Cochin-Bold" size:15.0];
    [title setFont:fontName];


    [headerView addSubview:title];
    [headerView addSubview:top];
    [headerView addSubview:bottom];

    return headerView;

}

}

heightForHeader

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {

NSLog(@"Height: %d",[tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0);
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section == 0]) {
    return 0;
} else {
    return 30;
}
}

numberOfRowsInSection

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {

int rows = 0;
for (Expense* exp in [dataHandler allMonthExpenses]) {
    if ([exp day].intValue == section) {
        rows++;
    }
}

return rows;
}

enter image description here sebastian

Predigestion answered 16/3, 2012 at 12:53 Comment(0)
N
58

What if in – tableView:viewForHeaderInSection: you return nil if the section count is 0.

EDIT : You can use numberOfRowsInSection for obtaining the number of elements in the section.

EDIT: Probably you should return nil also in titleForHeaderInSection if numberOfRowsInSection is 0.

EDIT: Did you implement the following method?

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

EDIT : Swift 3 example

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    switch section {
    case 0:
        if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
            return "Title example for section 1"
        }
    case 1:
        if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
            return "Title example for section 2"
        }
    default:
        return nil // when return nil no header will be shown
    }
    return nil
}
Nibelung answered 16/3, 2012 at 12:58 Comment(11)
sadly it does not work.. i updated the question with new code & a new screenshot.Tamra
no error. just finished editing the question. now the new code is in and the new screenshot.Tamra
by the way, did you implement numberOfRowsInSection?Nibelung
yes of course - otherwise i would not have any filled sections =) but for the sake of completion i added the function as well =)Tamra
every time that you do a comparation, you close with .... ==0]), is that ok?...i will use ...]==0)Nibelung
damn - sometimes i guess its best to stop working after some time ^^ that was of course an error, but it shifts the problem and doesnt solve it. now it makes a destinction between empty & filled sections. but the header is not gone. it now uses the standard header for zero-sections. (screenshot added in question).Tamra
added the updated code & a new screenshot. now i have the default header with a nil titlelable but the height doesnt go to 0 :/Tamra
and what if you check the size of the section header in the nib?..maybe if you use 0 you get a better result..since you are setting it in code.Nibelung
YES! it works :D you are my hero. found a last faulty comparison. thanks a million times for fighting through my long question =)Tamra
FYI: What makes this scary for me is that adding this line will break things: - (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{return [super tableView:tableView heightForHeaderInSection:section];} this is the sign of Apple trying to be too cleverHumidity
This is an old question now, but return nil; in titleForHeaderInSection is sufficient for me (after checking count > 0 for that section of course)Overweary
T
138

You have to set tableView:heightForHeaderInSection: to 0 for the appropriate sections. This is something which changed fairly recently and got me in a couple places. From UITableViewDelegate it says...

Prior to iOS 5.0, table views would automatically resize the heights of headers to 0 for sections where tableView:viewForHeaderInSection: returned a nil view. In iOS 5.0 and later, you must return the actual height for each section header in this method.

So you'll have to do something like

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return 0;
    } else {
        // whatever height you'd want for a real section header
    }
}
Taproot answered 16/3, 2012 at 13:17 Comment(8)
sadly it does not work.. i updated the question with new code & a new screenshot.Tamra
Static tables are a bit different. This code is for custom table header views, not just a title. If you are setting a title you will need to return nil for the method call tableView:titleForHeaderInSection: when appropriate. If you are using custom header views you must be overriding viewForHeaderInSection. If you are doing that, this code should work. If not, I would need more information to help with the issue.Taproot
awesome... thanks a lot. earlier i was returning a blank UIView like this: else { vw = [[UIView alloc]init]; [vw setHidden:YES]; }Personnel
i used viewforheader in section + above code and worked perfectely in ios 6 & even in lower version. thnnxx buddyyAuthenticity
Worked for me - I am on ios6Herve
It seems that if your table view is the first child of a navigation view, there's a bug: https://mcmap.net/q/187124/-how-to-hide-first-section-header-in-uitableview-grouped-styleDistillery
OK, but can someone explain why this doesn't work then? [tableView numberOfRowsInSection:section] == 0Strengthen
@Strengthen did you verify that you're returning nil in tableView:titleForHeaderInSection:?Demeanor
N
58

What if in – tableView:viewForHeaderInSection: you return nil if the section count is 0.

EDIT : You can use numberOfRowsInSection for obtaining the number of elements in the section.

EDIT: Probably you should return nil also in titleForHeaderInSection if numberOfRowsInSection is 0.

EDIT: Did you implement the following method?

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

EDIT : Swift 3 example

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    switch section {
    case 0:
        if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
            return "Title example for section 1"
        }
    case 1:
        if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
            return "Title example for section 2"
        }
    default:
        return nil // when return nil no header will be shown
    }
    return nil
}
Nibelung answered 16/3, 2012 at 12:58 Comment(11)
sadly it does not work.. i updated the question with new code & a new screenshot.Tamra
no error. just finished editing the question. now the new code is in and the new screenshot.Tamra
by the way, did you implement numberOfRowsInSection?Nibelung
yes of course - otherwise i would not have any filled sections =) but for the sake of completion i added the function as well =)Tamra
every time that you do a comparation, you close with .... ==0]), is that ok?...i will use ...]==0)Nibelung
damn - sometimes i guess its best to stop working after some time ^^ that was of course an error, but it shifts the problem and doesnt solve it. now it makes a destinction between empty & filled sections. but the header is not gone. it now uses the standard header for zero-sections. (screenshot added in question).Tamra
added the updated code & a new screenshot. now i have the default header with a nil titlelable but the height doesnt go to 0 :/Tamra
and what if you check the size of the section header in the nib?..maybe if you use 0 you get a better result..since you are setting it in code.Nibelung
YES! it works :D you are my hero. found a last faulty comparison. thanks a million times for fighting through my long question =)Tamra
FYI: What makes this scary for me is that adding this line will break things: - (float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{return [super tableView:tableView heightForHeaderInSection:section];} this is the sign of Apple trying to be too cleverHumidity
This is an old question now, but return nil; in titleForHeaderInSection is sufficient for me (after checking count > 0 for that section of course)Overweary
J
55

In my strange situation I have to return:

viewForHeaderInSection -> nil

 viewForFooterInSection -> nil (don't forget about footer!)

heightForHeaderInSection -> 0.01 (not zero!)

 heightForFooterInSection -> 0.01

only in this case empty sections disappear completely

Jae answered 14/12, 2013 at 12:3 Comment(4)
I did it just by setting headerHeight to 0.01.Aero
iOS 8.4 I could still see the text using 0.01, but 0.0 worked a charm override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if sectionedTableData[section].count == 0 { return CGFloat(0.0) } return CGFloat(40.0) }Benzidine
This works for me perfectly. Unfortunately I couldn't found this anywhere else. Thanks dude.Ponder
Even in iOS 12.1, you still have to set the height to 0.01.... iOS has some strange bugs.Bogusz
S
10

Take a look at the method -[UITableViewDelegate tableView:heightForHeaderInSection:]. Especially the note that accompanies its documentation:

Prior to iOS 5.0, table views would automatically resize the heights of headers to 0 for sections where tableView:viewForHeaderInSection: returned a nil view. In iOS 5.0 and later, you must return the actual height for each section header in this method.

Structural answered 16/3, 2012 at 13:16 Comment(2)
thanks so mch guys - ill update you in a couple of hours and will accept and upvote whatever worked best :-)Tamra
sadly it does not work.. i updated the question with new code & a new screenshot.Tamra
A
9

I know this is an old question but I'd like to add to it. I prefer the approach of setting the titleHeader to nil over altering the heightForHeaderInSection to 0 as it can cause problems with indexPath being +1 from where is should be due to the header still being there but hidden.

So with that said and building on DBD's answer you can set the titleForHeaderInSection: to nil for sections with no rows in it like so:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    } else {
        // return your normal return
    }
}
Allenaallenby answered 6/8, 2013 at 1:57 Comment(0)
U
7

In 2015 using iOS 8 and Xcode 6, the following worked for me:

/* Return the title for each section if and only if the row count for each section is not 0. */

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }else{

    // here you want to return the title or whatever string you want to return for the section you want to display

    return (SomeObject*)someobjectArray[section].title;
    }
}
Unsaddle answered 27/8, 2015 at 9:52 Comment(0)
L
5

This seems to be the proper way, It will animate correctly & works clean... as Apple intended...

Provide appropriate info to the tableView delegate

When no items in section, Return 0.0f in:

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

..Also return nil for:

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

Do appropriate data removal for tableView

  1. Call [tableView beginUpdates];
  2. Remove items from your dataSource, keeping track of where elements got removed..
  3. Call deleteRowsAtIndexPaths with the indexPaths of the cells you removed.
  4. if your datasource has no items in it (Here you would end up with just the header). Call reloadSections: to reload that section. This will trigger the correct animation and hide/slide/fade the header.
  5. Finally call [tableView endUpdates]; to finish the update..
Leilanileininger answered 4/3, 2015 at 18:43 Comment(2)
Thanks for the tip on reloadSections: :-) Still cant get anything to work for me with iOS11. Bit annoying :/Onepiece
Why do we have to call reloadSections? I noticed that if I don't call it, my table view headers overlap (until I scroll, then the correct header is shown).Bogusz
J
3

Swift 4.2

Set the heightForHeaderInSection to Zero and if you've a custom section view, set it to nil for sections without cells.

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

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

        return tableView.dataSource?.tableView(tableView, numberOfRowsInSection: section) == 0 ? nil: headerView(tableView: tableView, section: section)
    }
Jeffereyjefferies answered 16/4, 2019 at 0:6 Comment(0)
C
0

Here's how I'm doing this:

(kinda silly that this isn't the default behavior)

This is for if you're using auto sizing section headers, which it doesn't seem like the other answers are using, but I was.

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    if let headerView = self.tableView(tableView, viewForHeaderInSection: section) {
        return UITableView.automaticDimension
    } else {
        return 0
    }
}
Calculus answered 19/3 at 18:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.