Is it possible to disable floating headers in UITableView with UITableViewStylePlain?

IosObjective CIphoneCocoa TouchUikit

Ios Problem Overview


I'm using a UITableView to layout content 'pages'. I'm using the headers of the table view to layout certain images etc. and I'd prefer it if they didn't float but stayed static as they do when the style is set to UITableViewStyleGrouped.

Other then using UITableViewStyleGrouped, is there a way to do this? I'd like to avoid using grouped as it adds a margin down all my cells and requires disabling of the background view for each of the cells. I'd like full control of my layout. Ideally they'd be a "UITableViewStyleBareBones", but I didn't see that option in the docs...

Many thanks,

Ios Solutions


Solution 1 - Ios

A probably easier way to achieve this:

Objective-C:

CGFloat dummyViewHeight = 40;
UIView *dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, dummyViewHeight)];
self.tableView.tableHeaderView = dummyView;
self.tableView.contentInset = UIEdgeInsetsMake(-dummyViewHeight, 0, 0, 0);

Swift:

let dummyViewHeight = CGFloat(40)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
self.tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)

Section headers will now scroll just like any regular cell.

Solution 2 - Ios

(For who ever got here due to wrong table style) Change Table style from plain to grouped, via the attributes inspector, or via code:

let tableView = UITableView(frame: .zero, style: .grouped)

Solution 3 - Ios

WARNING: this solution implements a reserved API method. This could prevent the app from being approved by Apple for distribution on the AppStore.

I've described the private methods that turns of section headers floating in my blog

Basically, you just need to subclass UITableView and return NO in two of its methods:

- (BOOL)allowsHeaderViewsToFloat;
- (BOOL)allowsFooterViewsToFloat;

Solution 4 - Ios

In your Interface Builder click on your problem Table View

problem table view

Then navigate to Attributes Inspector and change Style Plain to Grouped ;) Easy

easy

Solution 5 - Ios

Ok, i know it is late but i had to do it. I have spent 10 hours by now searching for a working solution but did not find a complete answer. Did found some hints but difficult for starters to understand. So i had to put in my 2 cents and complete the answer.

As it has been suggested in the few of the answers the only working solution that i was able to implement is by inserting normal cells in the table view and handle them as Section Headers, but the better way to achieve it is by inserting these cells at row 0 of every section. This way we can handle these custom non-floating headers very easily.

So, the steps are.

  1. Implement UITableView with style UITableViewStylePlain.

     -(void) loadView
     {
         [super loadView];
     
         UITableView *tblView =[[UITableView alloc] initWithFrame:CGRectMake(0, frame.origin.y, frame.size.width, frame.size.height-44-61-frame.origin.y) style:UITableViewStylePlain];
         tblView.delegate=self;
         tblView.dataSource=self;
         tblView.tag=2;
         tblView.backgroundColor=[UIColor clearColor];
         tblView.separatorStyle = UITableViewCellSeparatorStyleNone;
     }
     
    
  2. Implement titleForHeaderInSection as usual ( you can get this value by using your own logic, but I prefer to use standard delegates ).

     - (NSString *)tableView: (UITableView *)tableView titleForHeaderInSection:(NSInteger)section
     {
         NSString *headerTitle = [sectionArray objectAtIndex:section];
         return headerTitle;
     }
    
  3. Immplement numberOfSectionsInTableView as usual

     - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
     {
         int sectionCount = [sectionArray count];
         return sectionCount;
     }
    
  4. Implement numberOfRowsInSection as usual.

     - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
     {
         int rowCount = [[cellArray objectAtIndex:section] count];
         return rowCount +1; //+1 for the extra row which we will fake for the Section Header
     }
    
  5. Return 0.0f in heightForHeaderInSection.

     - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
     {
         return 0.0f;
     }
    
  6. DO NOT implement viewForHeaderInSection. Remove the method completely instead of returning nil.

  7. In heightForRowAtIndexPath. Check if(indexpath.row == 0) and return the desired cell height for the section header, else return the height of the cell.

     - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
     {
         if(indexPath.row == 0)
         {
             return 80; //Height for the section header
         }
         else
         {
             return 70; //Height for the normal cell
         }
     }
    
  8. Now in cellForRowAtIndexPath, check if(indexpath.row == 0) and implement the cell as you want the section header to be and set the selection style to none. ELSE implement the cell as you want the normal cell to be.

     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     {
         if (indexPath.row == 0)
         {
             UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SectionCell"];
             if (cell == nil)
             {
                 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SectionCell"] autorelease];
                 cell.selectionStyle = UITableViewCellSelectionStyleNone; //So that the section header does not appear selected
                 
                 cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"SectionHeaderBackground"]];
             }
     
             cell.textLabel.text = [tableView.dataSource tableView:tableView titleForHeaderInSection:indexPath.section];
             
             return cell;
         }
         else
         {
             UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
             
             if (cell == nil) 
             {
                 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] autorelease];
                 cell.selectionStyle = UITableViewCellSelectionStyleGray; //So that the normal cell looks selected
    
                 cell.backgroundView =[[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CellBackground"]]autorelease];
                 cell.selectedBackgroundView=[[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"SelectedCellBackground"]] autorelease];
             }
     
             cell.textLabel.text = [[cellArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row -1]; //row -1 to compensate for the extra header row
             
             return cell;
         }
     }
    
  9. Now implement willSelectRowAtIndexPath and return nil if indexpath.row == 0. This will care that didSelectRowAtIndexPath never gets fired for the Section header row.

     - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
     {
         if (indexPath.row == 0)
         {
             return nil;
         }
         
         return indexPath;
     }
    
  10. And finally in didSelectRowAtIndexPath, check if(indexpath.row != 0) and proceed.

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (indexPath.row != 0)
        {
            int row = indexPath.row -1; //Now use 'row' in place of indexPath.row
            
            //Do what ever you want the selection to perform
        }
    }
    

With this you are done. You now have a perfectly scrolling, non-floating section header.

Solution 6 - Ios

You should be able to fake this by using a custom cell to do your header rows. These will then scroll like any other cell in the table view.

You just need to add some logic in your cellForRowAtIndexPath to return the right cell type when it is a header row.

You'll probably have to manage your sections yourself though, i.e. have everything in one section and fake the headers. (You could also try returning a hidden view for the header view, but I don't know if that will work)

Solution 7 - Ios

Change your TableView Style:

self.tableview = [[UITableView alloc] initwithFrame:frame style:UITableViewStyleGrouped];

As per apple documentation for UITableView:

> UITableViewStylePlain- A plain table view. Any section headers or > footers are displayed as inline separators and float when the table > view is scrolled. > > UITableViewStyleGrouped- A table view whose sections present distinct > groups of rows. The section headers and footers do not float.

Solution 8 - Ios

The interesting thing about UITableViewStyleGrouped is that the tableView adds the style to the cells and not to the TableView.

The style is added as backgroundView to the cells as a class called UIGroupTableViewCellBackground which handles drawing different background according to the position of the cell in the section.

So a very simple solution will be to use UITableViewStyleGrouped, set the backgroundColor of the table to clearColor, and simply replace the backgroundView of the cell in cellForRow:

cell.backgroundView = [[[UIView alloc] initWithFrame:cell.bounds] autorelease];

Solution 9 - Ios

There is another tricky way. The main idea is to double the section number, and first one only shows the headerView while the second one shows the real cells.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return sectionCount * 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section%2 == 0) {
        return 0;
    }
    return _rowCount;
}

What need to do then is to implement the headerInSection delegates:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    if (section%2 == 0) {
        //return headerview;
    }
    return nil;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    if (section%2 == 0) {
        //return headerheight;
    }
    return 0;
}

This approach also has little impact on your datasources:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    int real_section = (int)indexPath.section / 2;
    //your code
}

Comparing with other approaches, this way is safe while not changing the frame or contentInsets of the tableview. Hope this may help.

Solution 10 - Ios

For Swift 3+

Simply use UITableViewStyleGrouped and set the footer's height to zero with the following:

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

Solution 11 - Ios

Although this may not solve your problem, it did for me when I wanted to do a similar thing. Instead of setting the header I used the footer of the section above. What saved me was that this section was small and static in nature, so it never scrolled below the bottom of the view.

Solution 12 - Ios

The easiest way to get what you want is set your table style as UITableViewStyleGrouped, separator style as UITableViewCellSeparatorStyleNone:

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return CGFLOAT_MIN; // return 0.01f; would work same 
}

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

Do not try return footer view as nil, don't forget set header height and header view, after you must get what you desired.

Solution 13 - Ios

Maybe you could use scrollViewDidScroll on the tableView and change the contentInset based on the current visible header.

It seems to work for my use case!

extension ViewController : UIScrollViewDelegate{

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard   let tableView = scrollView as? UITableView,
            let visible = tableView.indexPathsForVisibleRows,
            let first = visible.first else {
            return
        }
    
        let headerHeight = tableView.rectForHeader(inSection: first.section).size.height
        let offset =  max(min(0, -tableView.contentOffset.y), -headerHeight)
        self.tableView.contentInset = UIEdgeInsetsMake(offset, 0, -offset, 0)
   }
}

Solution 14 - Ios

While thinking how to approach this problem, I remembered a very important detail about UITableViewStyleGrouped. The way UITableView implements the grouped style (the rounded borders around the cells) is by adding a custom backgroundView to the UITableViewCells, and not to the UITableView. Each cell is added a backgroundView according to its position in the section (upper rows get the upper part of the section border, middle ones get the side border and the bottom one gets – well, the bottom part). So, if we just want a plain style, and we don’t have a custom backgroundView for our cells (which is the case in 90% of the times), then all we need to do is use UITableViewStyleGrouped, and remove the custom background. This can be done by following those two steps:

Change our tableView style to UITableViewStyleGrouped Add the following line to cellForRow, just before we return the cell:

cell.backgroundView=[[[UIView alloc] initWithFrame:cell.bounds] autorelease];

And that’s it. The tableView style will become exactly like UITableViewStylePlain, except for the floating headers.

Hope this helps!

Solution 15 - Ios

To remove the floating section header sections completely, you can do this:

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

return nil doesn't work.

To disable floating but still show section headers you can provide a custom view with its own behaviours.

Solution 16 - Ios

Another way to do it is to make an empty section right before the one you want the header on and put your header on that section. Because the section is empty the header will scroll immediately.

Solution 17 - Ios

You can add one Section(with zero rows) above, then set the above sectionFooterView as current section's headerView, footerView doesn't float. Hope it gives a help.

Solution 18 - Ios

Ignore XAK. Do not explore any private methods if you want your app to have the chance of being accepted by apple.

This is easiest if you are using Interface Builder. You would add a UIView at the top of the view (where the images will go), then add your tableview below that. IB should size it accordingly; that is, the top of the tableview touches the bottom of the UIView you've just added and it's height covers the rest of the screen.

The thinking here is that if that UIView is not actually part of the table view, it will not scroll with the tableview. i.e. ignore the tableview header.

If you're not using interface builder, it's a little more complicated because you've got to get the positioning and height correct for the tableview.

Solution 19 - Ios

Check the answer how to implement headers with StoryBoard: https://stackoverflow.com/questions/7841167/table-header-views-in-storyboards

Also notice that if you don't implement

viewForHeaderInSection:(NSInteger)section

it will not float which is exactly what you want.

Solution 20 - Ios

A variation on @samvermette's solution:

/// Allows for disabling scrolling headers in plain-styled tableviews
extension UITableView {

    static let shouldScrollSectionHeadersDummyViewHeight = CGFloat(40)

    var shouldScrollSectionHeaders: Bool {
        set {
            if newValue {
                tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: bounds.size.width, height: UITableView.shouldScrollSectionHeadersDummyViewHeight))
                contentInset = UIEdgeInsets(top: -UITableView.shouldScrollSectionHeadersDummyViewHeight, left: 0, bottom: 0, right: 0)
            } else {
                tableHeaderView = nil
                contentInset = .zero
            }
        }

        get {
            return tableHeaderView != nil && contentInset.top == UITableView.shouldScrollSectionHeadersDummyViewHeight
        }
    }

}

Solution 21 - Ios

The snippet display a sticky header only for the first section. Others section headers will float with a cells.

func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
    
    if section == 1 {
        tableView.contentInset = .zero
    }
    
}

func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
    if section == 0 {
        tableView.contentInset = .init(top: -tableView.sectionHeaderHeight, left: 0, bottom: 0, right: 0)
    }
}

Solution 22 - Ios

A tricky way is add an empty section for header. Because section has no cell, it will not floating at all.

Solution 23 - Ios

**Swift 5.3 | Programmatically **

private func buildTableView() -> UITableView {
    let tableView = UITableView()
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.rowHeight = UITableView.automaticDimension
    tableView.showsVerticalScrollIndicator = false
    tableView.separatorStyle = .none
    let dummyViewHeight: CGFloat = 80
    tableView.tableFooterView = UIView(
        frame: CGRect(x: .zero,
                      y: .zero,
                      width: tableView.bounds.size.width,
                      height: dummyViewHeight))

    tableView.contentInset = UIEdgeInsets(top: .zero, left: .zero, bottom: -dummyViewHeight, right: .zero)
    return tableView
}

Solution 24 - Ios

Best solution for below 2 scenarios:

Scenario 1: If the tableView style is not .grouped. Have no problem with changing it to grouped. footer wont float.

Scenario 2: If you want only one footer at the end of a tableView. and It's style is .plain

Steps:

  1. add one more section at the end.
  2. make sure in added section have (nuberOfRowsInSection method) zero number of rows.
  3. add custom footer to that section.
  4. set heightForFooterInSection to your custom footer and set .zero to other sections' footers.

example :

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

//note: Filter the section where you intended to add custom section

    let footerView = UIView()
    submitOrganiser?.showFooterAtTheEnd(view: footerView) //my method to customise the footer, use your implementation
    return footerView
}

Solution 25 - Ios

If you don't mind using a UICollectionView, this can also be achieved using UICollectionView list configurations in iOS 13.0+ and using headerMode set to .firstItemInSection.

override func viewDidLoad() {
    super.viewDidLoad()
    var config = UICollectionLayoutListConfiguration.init(appearance: .plain)
    config.headerMode = .firstItemInSection
    let listLayout = UICollectionViewCompositionalLayout.list(using: config)
    self.collectionView.collectionViewLayout = listLayout
}

Solution 26 - Ios

This can be achieved by assigning the header view manually in the UITableViewController's viewDidLoad method instead of using the delegate's viewForHeaderInSection and heightForHeaderInSection. For example in your subclass of UITableViewController, you can do something like this:

- (void)viewDidLoad {
    [super viewDidLoad];
           
    UILabel *headerView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 40)];
    [headerView setBackgroundColor:[UIColor magentaColor]];
    [headerView setTextAlignment:NSTextAlignmentCenter];
    [headerView setText:@"Hello World"];
    [[self tableView] setTableHeaderView:headerView];
}

The header view will then disappear when the user scrolls. I don't know why this works like this, but it seems to achieve what you're looking to do.

Solution 27 - Ios

Maybe you can simply make header view background transparent:

- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
    view.tintColor = [UIColor clearColor];
}

Or apply it globally:

    [UITableViewHeaderFooterView appearance].tintColor = [UIColor clearColor];

Solution 28 - Ios

I have another even simpler solution, to be used without autolayout and with everything done through the XIB :

1/ Put your header in the tableview by drag and dropping it directly on the tableview.

2/ In the Size Inspector of the newly made header, just change its autosizing : you should only leave the top, left and right anchors, plus the fill horizontally.

That's how it should be set for the header

That should do the trick !

Solution 29 - Ios

According to @samvermette's answer,I've implemented the above code in Swift to make it easy for coders to use swift.

let dummyViewHeight = CGFloat(40)
self.tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.bounds.size.width, height: dummyViewHeight))
self.tableView.contentInset = UIEdgeInsetsMake(-dummyViewHeight, 0, 0, 0)

Solution 30 - Ios

ref: https://stackoverflow.com/a/26306212

let tableView = UITableView(frame: .zero, style: .grouped)

PLUSE

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

    return 0
}

This helped to use header space

Solution 31 - Ios

you can easily achieve this by implementing the viewForHeaderInSection method in the tableview delegate class. this method expects a UIView as return object (which is your header view). i have done this same thing in my code

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTrickyView Question on Stackoverflow
Solution 1 - IossamvermetteView Answer on Stackoverflow
Solution 2 - Iosdavid72View Answer on Stackoverflow
Solution 3 - IosXAKView Answer on Stackoverflow
Solution 4 - IosHannahCarneyView Answer on Stackoverflow
Solution 5 - IosanemoView Answer on Stackoverflow
Solution 6 - IosfrankodwyerView Answer on Stackoverflow
Solution 7 - IosAshView Answer on Stackoverflow
Solution 8 - IosadamsitonView Answer on Stackoverflow
Solution 9 - IosLaneView Answer on Stackoverflow
Solution 10 - IosTamás SengelView Answer on Stackoverflow
Solution 11 - IosTorbjörnView Answer on Stackoverflow
Solution 12 - IosMichail PidgorodetskiyView Answer on Stackoverflow
Solution 13 - IosMartinView Answer on Stackoverflow
Solution 14 - IosJames LaurenstinView Answer on Stackoverflow
Solution 15 - IosdjskinnerView Answer on Stackoverflow
Solution 16 - IosdevguydavidView Answer on Stackoverflow
Solution 17 - IosLee LamView Answer on Stackoverflow
Solution 18 - IosimnkView Answer on Stackoverflow
Solution 19 - IosRaphael OliveiraView Answer on Stackoverflow
Solution 20 - IosrafView Answer on Stackoverflow
Solution 21 - IosBlazej SLEBODAView Answer on Stackoverflow
Solution 22 - IosWillView Answer on Stackoverflow
Solution 23 - IosMaatheusGoisView Answer on Stackoverflow
Solution 24 - IosSantoshView Answer on Stackoverflow
Solution 25 - IosMH175View Answer on Stackoverflow
Solution 26 - IosJohnny OshikaView Answer on Stackoverflow
Solution 27 - IosTinpontView Answer on Stackoverflow
Solution 28 - IosCyberDandyView Answer on Stackoverflow
Solution 29 - IosBingerzView Answer on Stackoverflow
Solution 30 - IosPratikView Answer on Stackoverflow
Solution 31 - IosRajView Answer on Stackoverflow