27

I have a grouped UITableView where not all sections may be displayed at once, the table is driven by some data that not every record may have. My trouble is that the records that don't have certain sections show up as blank spaces in the table. There are no footers/headers. Anything I've missed?

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self getRowCount:section];
}


// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}

cell.textLabel.text = [NSString stringWithFormat:@"section: %d row: %d",indexPath.section,indexPath.row];
// Configure the cell...

return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    float height = 0.0;
    
    if([self getRowCount:indexPath.section] != 0){
        height = kDefaultRowHeight;
    }
    
    return height;
    
}
Zoe
  • 27,060
  • 21
  • 118
  • 148
christo16
  • 4,843
  • 5
  • 42
  • 53

11 Answers11

45

I have a similar situation and my data model really works better if I can let it have empty sections.

Your problem can be solved if you set self.tableView.sectionHeaderHeight = 0; and self.tableView.sectionFooterHeight = 0; in your tableview controller. These must be 0 because the delegate methods somehow ignore a return value of 0. Then you have to override some delegate methods:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    if (section==0)
        return sectionGapHeight;
    if ([self tableView:tableView numberOfRowsInSection:section]==0) {
        return 0;
    }
    return sectionGapHeight;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    if (section==[self numberOfSectionsInTableView:tableView]-1) {
        return sectionGapHeight;
    }
    return 0;
}
- (UIView *)sectionFiller {
    static UILabel *emptyLabel = nil;
    if (!emptyLabel) {
        emptyLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        emptyLabel.backgroundColor = [UIColor clearColor];
    }
    return emptyLabel;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [self sectionFiller];
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    return [self sectionFiller];
}

This code will also make the gap between sections the same height as the gap before the first section and the gap below the last. That looks better in my opinion than the default where the gap between is twice as big as the ones at the top and the bottom.

user362178
  • 818
  • 6
  • 10
  • 3
    The least intrusive method I've found until now is to return 1 for heightForHeaderInSection and heightForFooterInSection `if ([self tableView: tableView numberOfRowsInSection: section] == 0)` and return a view with zero size for `viewForHeaderInSection` / `viewForFooterInSection` if the row count is 0. – Steven Kramer May 27 '11 at 12:38
  • 1
    Thanks! Thanks! Freaking Apple. It would be really nice if they documented things like the fact that returning 0 for heights in the delegate does not override the standard spacing. There are way too many subtle API edge cases that Apple doesn't document. – smparkes Oct 02 '11 at 22:54
  • 3
    This logic does not account for situations where some of your sections have title text or footers. – Sonny Saluja Apr 17 '12 at 20:58
  • Use CGFLOAT_MIN instead of 1 – manuelwaldner Mar 21 '17 at 10:23
  • self.tableView.sectionHeaderHeight = 0 && self.tableView.sectionFooterHeight = 0 have solved blank space between hidden section. Thanks – Shrawan Oct 23 '17 at 09:11
  • Thanks! I was looking everywhere for a solution, as I already suspected the sectionHeader heights, but I didn't think the delegate value would be ignored... – ph1lb4 Dec 03 '21 at 12:10
20

Here is the solution that worked for me

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    switch (section) {  
      case 1: case 3: case 5: return 20.0f; break;
      default: return 0.01f; // for some reason 0 is not accepted - give something :)
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    switch (section) {  
      case 1: case 3: case 5: return 20.0f; break;
      default: return 0.01f; // for some reason 0 is not accepted - give something :)
    }
}
Ramesh
  • 1,703
  • 18
  • 13
  • 1
    I read this and thought "there's no way that it just doesn't like 0.0f". But I checked it out and as of iOS6, this is still the case. – Tin Can Jul 10 '13 at 19:46
6

In the above screenshot, is your numberOfSectionsInTableView: method returning a value of 5? And then is your getRowCount: method (called from numberOfRowsInSection:) returning a value of 0 for those "missing" sections (e.g. 1, 3 and 4)?

If you don't want gaps in between, the likely solution is to only declare the number of sections you want to be visible. In your example above, you'd only want to return value of 3 from numberOfSectionsInTableView:.

Shaggy Frog
  • 27,575
  • 16
  • 91
  • 128
5

This solution is based on user362178 solution. But keeps the labels of the sections intact:

Define somewhere at the top of your code:

#define HEIGHT_FOR_HEADER_AND_FOOTER 50

Add to the view did load:

self.tableView.sectionHeaderHeight = 0;
self.tableView.sectionFooterHeight = 0; 

Add these methods:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{
    // Needed because if this does not return a value, the header label of the section will
    // be at the top of the screen.
    if (section == 0)
    {
        return HEIGHT_FOR_HEADER_AND_FOOTER;
    }

    if ([self tableView:tableView numberOfRowsInSection:section] == 0) 
    {
        return 0;
    }
    else   
    {
        return HEIGHT_FOR_HEADER_AND_FOOTER;
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section 
{
    // if last section
    if (section == [self numberOfSectionsInTableView:tableView] - 1) 
    {
        return HEIGHT_FOR_HEADER_AND_FOOTER;
    }

    return 0;
}
MrHus
  • 32,888
  • 6
  • 31
  • 31
3

Here is the most elegant solution I've found based on all the answers here, plus this answer:

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

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

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    if ([self tableView: tableView numberOfRowsInSection: section] == 0) {
        return 0.01f;;
    }

    return UITableViewAutomaticDimension;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    if ([self tableView: tableView numberOfRowsInSection: section] == 0) {
        return 0.01f;;
    }

    return UITableViewAutomaticDimension;
}
Community
  • 1
  • 1
kwahn
  • 2,118
  • 2
  • 21
  • 17
1

I had the same problem in iOS 7 and finally I solved this checking the Height for Header and Footer sections, as Ramesh said:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 
if ([[self.tableView objectAtIndex:section] count] == 0)
    return 0.01f;
else
    return 30.f;

}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0.01f;

}

It's curious, but if you set 0.f as the height the sections is showed with a default size and moves down the other sections.

j_gonfer
  • 1,547
  • 21
  • 21
1

There is a convenient value for this case, called CGFloat.leastNonzeroMagnitude. Use it as follows:

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

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

You can also use tableView.sectionHeaderHeight = 0 and tableView.sectionFooterHeight = 0, but this will be applied for all headers and footers. With the delegate solution you're still able to manipulate different headers' and footers' height, but use .leastNonzeroMagnitude as a default

ramzesenok
  • 5,469
  • 4
  • 30
  • 41
0

The key is to set the table view's sectionHeaderHeight and sectionFooterHeight to zero.

You can do this either in viewDidLoad, or in the User Defined Runtime Attributes section in your storyboard/xib. For some reason, in the Size Inspector tab, you cannot set the header or footer height below 1.

I found that once I did this, I didn't need to override any other delegate methods in a special way. I simply return 0 from numberOfRowsInSection and nil from titleForHeaderInSection for sections that are empty, and they take up no space in my table view.

Sections that do have titles are automatically given the proper header size, and the top of the table view still has the default blank space.

Chris Vasselli
  • 13,064
  • 4
  • 46
  • 49
0

I believe that with section table views, each section must exist, and each section has buffer room for a header and a footer regardless of you putting text in them. What you need to do is remove the sections that aren't being used. In your example, you would remove section 1, 3, and 4. This would mean:

"section 0" is indexPath.section == 0

"section 2" is indexPath.section == 1

"section 5" is indexPath.section == 2

If you code in this fashion, the headers and footers will be removed because the nil cell is removed.

This isn't proper coding of course but I just wanted to give you an idea of the theology. Hope this helps.

W Dyson
  • 4,604
  • 4
  • 40
  • 68
0

I solved the issue by setting tableView's dataSource and delegate where I initialize the tableView.

Note: remember you might need to make tableView lazy to assign when initializing the tableView.

lazy var tableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .grouped)
        tableView.separatorStyle = .none
        tableView.backgroundColor = .white
        tableView.dataSource = self
        tableView.delegate = self
        return tableView
    }()
Murat Yasar
  • 994
  • 9
  • 24
0

I solved the issue by setting tableView's dataSource and delegate where I initialize the tableView

Remember you might need to make tableView lazy to assign when initializing the tableView

lazy var tableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .grouped)
        tableView.separatorStyle = .none
        tableView.backgroundColor = .white
        tableView.dataSource = self
        tableView.delegate = self
        return tableView
    }()
Murat Yasar
  • 994
  • 9
  • 24