Hunter
Hunter

Reputation: 4371

UITableView Not Respecting heightForHeaderInSection/heightForFooterInSection?

I have a UITableView where in some instances, certain sections have zero rows. My goal is that when this is true, I don't want any wasted space in the table view, it should look like there's no data.

The problem I'm having is with the header and footer for the sections, which are showing even if there's no row and despite me overriding the delegate method to return 0.0f.

Here's what it looks like - you can see the ~20p of gray space at the top there, headers and footers of about 10p each for a section with 0 rows.

alt text
(source: hanchorllc.com)

Here's my pseudo code:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
     if ([section hasRow]) {
          return 10.0f;
     } else {
          return 0.0f;
     }
}



- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
     if ([section hasRow]) {
          return 10.0f;
     } else {
          return 0.0f;
     }
}

I have verified that these methods are being called and that the proper execution path is taking place.

One wrinkle - this view controller is using a XIB and that UITableView has the section header and footer values set at 10.0 (default), though I thought that was overriden by the delegate method, if implemented.

This is an app targeting 3.0.

What am I doing wrong?

Upvotes: 27

Views: 29587

Answers (8)

dronpopdev
dronpopdev

Reputation: 817

I use the following solution:

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

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

Result:

result

Upvotes: 0

Alistair Cooper
Alistair Cooper

Reputation: 291

I found that in my case 2 things were needed to slay the phantom footer:

1) setting the table view sectionFooterHeight to 0, i.e. in viewDidLoad adding:

tableView.sectionFooterHeight = 0

2) adding the delegate method:

override func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    return UIView(frame: CGRect.zero)
}

(using Swift 2.2)

Upvotes: 5

Artem Goryunov
Artem Goryunov

Reputation: 71

It works for me:

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    tableView.sectionHeaderHeight = (section == 0 ? 10 : 0);
    return tableView.sectionHeaderHeight;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    tableView.sectionFooterHeight = (section == 0 ? 20 : 4);
    return tableView.sectionFooterHeight;
}

Upvotes: 1

Juris V
Juris V

Reputation: 371

This is a bit tricky, as 0.0f value is not accepted. but anything close enough to zero will do the trick. If You decide not to be pixel perfect and want the round numbers, then 1.0f will do almost the same, as 1px difference in height will be fairly noticable.

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    if(section == 1 )
        return 0.000001f;
    else return 44.0f; // put 22 in case of plain one..
}

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
    return 0.000001f; //removing section footers
}

Upvotes: 37

mark
mark

Reputation: 36

My solution to this problem is on and off how h4xxr is describing however, I have a slightly different approach to constructing a dynamic number of sections.

Firstly, I define a method that will walk my data structure and flag wether or not the data should be shown in the table. This flag is stored in an array so that I can include as few or as many sections as I like. I can describe this implementation a little better if we focused on just the number of cells for each section:

Suppose my first section is to be contact details with first name, last name and age. I will define this section with an id of '1' (although technically as I explain later this could be any number). Now, if my method determines that this section should be visible, I push the value '1' onto an array.

The next section I wish to show is address and may have 3-4 rows/cells. As above, the logic in my method determines wether or not the address should be visible by pushing say '2' onto the array.

Now.. ..if we wanted both sections visible, we would have an array with a length of 2 and two items [1,2]. If we only wanted the contact details visible our array would have a length of 1 with items [1] and [2] for the address only.

Now we can return the length of our array to define the number of sections we want.

our switch statement would now look like something like this:

switch(ourArray[indexPath.section]){
    case 1:
        <Return the number of rows for the contact details which we said would be 3>
        break;
    case 2:
        <Return the number of rows for the address details which we said would be 4>
        break;
    case 3:
        <Return another number for some section that is referenced by the id '3'>
        break; 
}

Notice I've put case 3 in my switch. Because the array in our example only contains values '1' and '2', case 3 would never be matched and so ignored unless we decided to add / enable this case by pushing it to the array.

Note also that we can in our method that defines the logic of which sections are visible, change the order of the sections by inserting/pushing them at different locations in the array.

I use the above religiously as it allows me to decouple the indexing and construction of sections.

Finally, Before I update my table by using 'reloadData' I will call my method which will construct the array and provide the correct length and sequence of section id's to allow my tableview to know how to build itself. If my data changes or is filtered somehow, I will again call this method and reconstruct the array.

Upvotes: 0

Jeff Mascia
Jeff Mascia

Reputation: 564

It appears that the table respects tableView:heightForHeaderInSection: only if tableView:viewForHeaderInSection: is not nil, or if tableView:titleForHeaderInSection: is not nil or @"". The same is true respectively for footer heights.

So assuming you don't have any section header views or titles, just add this to your table delegate:

- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
  return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
}

Upvotes: 9

Davidm
Davidm

Reputation: 186

I had a similar problem: I was loading a UITableView from a XIB (same as you), and supplied a 0 height for some section footers with tableView:heightForFooterInSection (same as you), but the value was ignored.

The fix was simple: also set the footer height to 0.0 in the XIB. (In Interface Builder select the Table View, press Command-3 to view the Size Inspector, look for the footer height field near the top)

Once that was done it obeyed the custom footer height as expected. (Perhaps it treats the XIB footer height as a minimum?)

Upvotes: 3

h4xxr
h4xxr

Reputation: 11475

In a "grouped" UITableView on the iPhone it will still render a minimum height for header and footer, effectively ignoring your code to set it to zero. It is not linked to the XIB default.

This is because a zero height section header or footer would look very odd. Therefore Apple has decreed that a header height cannot be set to 0. And therefore multiple "empty" sections will render oddly as per your screenshot.

I'm afraid it's all because you're going the wrong way about clearing the header size when there are no data rows; instead you should not be calling any methods for empty sections. It is a bad technique because essentially the iPhone is having to call more methods than it ought to, and also you render more headers than you want to (usually - sometimes people want to leave the blank headers there, for example for drag and drop).

So, for example, let's imagine that you have a number of sections, but only some of them have more than one row in (perhaps based on user settings/filters).

The wrong way of implementing it is:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return totalNumberOfPossibleSections;
}

and then for each section you have no result for:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  if (![section hasRow]) return 0;
}

and

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
  if (![section hasRow]) return 0.0f;
}

The correct way of implementing it is:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return numberOfSectionsWhichHaveAtLeastOneRowInThem;
}

and then each section will have at least one result. That way, sections with no data aren't even rendered, methods aren't even called for them. Note: my two variable names are made up to convey what they'll contain! They're not special Apple variables...

Hope that helps!

Upvotes: 26

Related Questions