154

I have a UITableView with two sections. It is a simple table view. I am using viewForHeaderInSection to create custom views for these headers. So far, so good.

The default scrolling behavior is that when a section is encountered, the section header stays anchored below the Nav bar, until the next section scrolls into view.

My question is this: can I change the default behavior so that the section headers do NOT stay anchored at the top, but rather, scroll under the nav bar with the rest of the section rows?

Am I missing something obvious?

Thanks.

TheNeil
  • 3,321
  • 2
  • 27
  • 52
davidjhinson
  • 1,695
  • 2
  • 12
  • 6
  • 1
    Check here for the right way to achieve this effect http://stackoverflow.com/a/13735238/1880899 – anemo Nov 28 '13 at 03:25

19 Answers19

175

The way I solved this problem is to adjust the contentOffset according to the contentInset in the UITableViewControllerDelegate (extends UIScrollViewDelegate) like this:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
       CGFloat sectionHeaderHeight = 40;
   if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {
       scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
   } else if (scrollView.contentOffset.y>=sectionHeaderHeight) {
       scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
   }
}

Only problem here is that it looses a little bit of bounce when scrolling back to the top.


{NOTE: The "40" should be the exact height of YOUR section 0 header. If you use a number that is bigger than your section 0 header height, you'll see that finger-feel is affected (try like "1000" and you'll see the bounce behaviour is sort of weird at the top). if the number matches your section 0 header height, finger feel seems to be either perfect or near-perfect.}

Fattie
  • 27,874
  • 70
  • 431
  • 719
awulf
  • 2,362
  • 1
  • 15
  • 12
  • 4
    Thank you! This is a perfect solution. Do you have the solution for the section footer? – Tuan Nguyen Jan 11 '12 at 07:49
  • 12
    This solution does not handle tap-on-menubar correctly, which is supposed to scroll to the top of the list. – Reid Ellis May 02 '12 at 18:47
  • if u need the same behaviour for section footer view just change the last line like this:scrollView.contentInset = UIEdgeInsetsMake(0, 0, sectionHeaderHeight, 0) – alex Oct 03 '12 at 07:35
  • This seems to be enough: scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentOffset.y >= 0 ? -scrollView.contentOffset.y : 0, 0, 0, 0); – MacMark Feb 14 '13 at 11:30
  • @MacMark, it won't be enough, because in that case you hav infinite down scroll. – ArVan Apr 30 '13 at 11:12
  • There's also a problem with index bars – Stavash Jun 10 '13 at 13:39
  • I had issue with this solution when keyboard visible and hide, the bellow VoidSterm answer is works fine for me in iOS7 too http://stackoverflow.com/a/5127682/453542 – Naveen Shan Jul 29 '13 at 10:06
  • this solution was in one of projects, i've worked on, and it's making nuts why the heck section headers do not stick to the navigation bar. It's ugly hack, not a solution. Sad that it's the best, what i've able to found, when i was need such behaviour. – trickster77777 Nov 02 '13 at 08:50
  • Very good solution but doesn't work with `tableView.bounce = NO` – nitish005 Nov 18 '15 at 12:36
  • how can i vary the " CGFloat sectionHeaderHeight = 40; " this one .. bcz i'm having multiple headerviews, with multiple heights , how can i change this value dynamically – MANCHIKANTI KRISHNAKISHORE Apr 25 '17 at 09:06
  • When tap the status bar, the scrollView scrolls to top, but the contentInset is not change. – Hilen May 04 '17 at 07:13
86

You can also add a section with zero rows at the top and simply use the footer of the previous section as a header for the next.

voidStern
  • 3,678
  • 1
  • 29
  • 32
36

Were it me doing this, I'd take advantage of the fact that UITableViews in the Plain style have the sticky headers and ones in the Grouped style do not. I'd probably at least try using a custom table cell to mimic the appearance of Plain cells in a Grouped table.

I haven't actually tried this so it may not work, but that's what I'd suggest doing.

Colin Barrett
  • 4,451
  • 1
  • 26
  • 33
  • Probably would work, but has it been tried? And it seems like a lot of work when @voidStern's answer works perfectly!! – bentford Apr 12 '11 at 20:37
  • voidStern's answer doesn't work for me when I have more than two sections. Colin's answer is simpler/more elegant, without the need to manually shift section number and add section count. – Joseph Lin May 11 '11 at 20:42
  • Tips: use `tableView.separatorColor = [UIColor clearColor];` to mimic a plain table view. – Joseph Lin May 11 '11 at 20:42
  • FYI, this works well. I haven't implemented this specifically but I have made custom cells that look like plain cells in a grouped table. – XJones Nov 16 '11 at 23:47
  • 3
    I tried doing this, but when I couldn't make the grouped table cells look like the plain ones, i.e. remove the left and right margin either side of the cells. How did you do this? – Adam Carter Aug 08 '12 at 14:32
  • This is the right solution for my situation, but in my case I also needed to cell.backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; and cell.backgroundView.backgroundColor = [UIColor clearColor]; to remove the grouped backgrounds from the sections. – deepwinter Apr 15 '13 at 02:50
  • This method worked well for me. Only issue was getting the cells to be the correct size, so in my UITableViewCell subclass I overwrite the setFrame: function to grow the frame by 10px horizontally. Even though the final cell frame width ends up being 340px, the internal cell views were all drawn correctly after the grouped cell insets were added back in. Here's some sample code: - (void)setFrame:(CGRect)frame { CGFloat inset = 10.0f; frame.origin.x -= inset; frame.size.width += 2 * inset; [super setFrame:frame]; } – Mr. T May 07 '13 at 09:49
  • I know this is kind of old, but for anyone still looking at this, this is the simplest solution. To add some code to this answer, I just called self = [super initWithStyle:UITableViewStyleGrouped]; in my initWithStyle: method, instead of the usual self = [super initWithStyle:style]; – baptzmoffire Nov 02 '13 at 00:08
  • This would have been the good answer except my designer is picky and wanted *some* headers to stick and *others* that don't. A true nightmare! – Flying_Banana Jun 29 '15 at 12:55
  • 1
    Thanks a lot for making things clear about UITableViewStyle i.e grouped and plain, which decides whether tableview's section header/footer will be sticky or not. Thanks much @Colin Barrett – Dhaval H. Nena Feb 02 '17 at 06:44
  • If you want the grouped table to look like the plain table, change the heightForHeader and Footer to return 0.1 (returning 0 will use the default value instead) – Jason Oct 10 '19 at 22:52
28

I know it comes late, but I have found the definitive solution!

What you want to do is if you have 10 sections, let the dataSource return 20. Use even numbers for section headers, and odd numbers for section content. something like this

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section%2 == 0) {
        return 0;
    }else {
        return 5;
    }
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section%2 == 0) {
        return [NSString stringWithFormat:@"%i", section+1];
    }else {
        return nil;
    }
}

Voilá! :D

LocoMike
  • 5,626
  • 5
  • 30
  • 43
  • great idea. but if you have section index titles, I suspect this will mess them up. – roocell Jan 12 '12 at 14:53
  • why? you can do the same with section titles. Just remember nil for odd numbers and a title for the even number. – LocoMike Jan 12 '12 at 19:43
  • 1
    yes - this method works great. It was a lot of bookkeeping (probably to do with the way i have to get the section titles) But it worked. The trick is that numberOfRowsInSection and cellForRowAtIndexPath uses if (section%2!=0 && section!=0) whereas the titleForHeaderInSection and the other section index functions use if (section%2==0) – roocell Jan 13 '12 at 15:38
  • the originator should choose this as the answer. It'd be great if there was a way to subclass uitableview to anchor the section titles automatically. – roocell Jan 13 '12 at 16:02
  • This worked the best for me. I tried @Colin's solution first, but it didn't work well with the heavily customized `UITableView` that I'm using. – kubi Mar 13 '12 at 19:10
16

Originally posted Here, a quick solution using the IB. The same can be done programmatically though quite simply.

A probably easier way to achieve this (using IB):

Drag a UIView onto your TableView to make it its header view.

  1. Set that header view height to 100px
  2. Set the tableview contentInset (top) to -100
  3. Section headers will now scroll just like any regular cell.

Some people commented saying that this solution hides the first header, however I have not noticed any such issue. It worked perfectly for me and was by far the simplest solution that I've seen so far.

Community
  • 1
  • 1
Doc
  • 1,480
  • 2
  • 16
  • 28
  • Note that you should probably set the view to use a background color of clearcolor, otherwise if you scroll past the top you see white in the bounce. – Doc Jan 30 '13 at 17:21
  • 3
    It does hides my first header – Leena Jul 06 '13 at 06:08
  • Leena, what version of iOS are you running on? Is the view a UITableViewController, or a UIView with a UITableView in it? I'm not sure why for some people this solution hides the first header so I'm trying to find any discrepancies. – Doc Jul 08 '13 at 13:44
  • This solution is perfect. I'm looking for an infinite top and bottom cell background appearance - this totally accomplishes it. Nice! – Taylor Halliday Sep 24 '13 at 19:25
  • This solution is excellent. Easier to accomplish than the other answers in this thread. In my opinion, this should be the accepted answer. – John Rogers Oct 13 '13 at 23:16
15

I was not happy with the solutions described here so far, so I tried to combine them. The result is the following code, inspired by @awulf and @cescofry. It works for me because I have no real table view header. If you already have a table view header, you may have to adjust the height.

// Set the edge inset
self.tableView.contentInset = UIEdgeInsetsMake(-23.0f, 0, 0, 0);

// Add a transparent UIView with the height of the section header (ARC enabled)
[self.tableView setTableHeaderView:[[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 23.0f)]];
Thomas Kekeisen
  • 4,355
  • 4
  • 35
  • 54
  • This was a simplest solution I found. – Zorayr Oct 25 '13 at 06:27
  • This worked for me on iOS9.2, so even though an older answer, it is still valid. And it didn't hide the first section header. Works, and works perfectly. – idStar Jan 30 '16 at 21:16
  • Perfect. If your tableview already has a header, just increase its height by the offset then increase the header content's top constraint constant to shift the content down to the original position – Jason Oct 17 '19 at 21:14
15

Just change TableView Style:

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

UITableViewStyle Documentation:

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.

Arthur S
  • 337
  • 3
  • 8
15

There are several things that need done to solve this problem in a non-hacky manner:

  1. Set the table view style to UITableViewStyleGrouped
  2. Set the table view backgroundColor to [UIColor clearColor]
  3. Set the backgroundView on each table view cell to an empty view with backgroundColor [UIColor clearColor]
  4. If necessary, set the table view rowHeight appropriately, or override tableView:heightForRowAtIndexPath: if individual rows have different heights.
Neil Gall
  • 579
  • 4
  • 11
  • (+1) @Neil, I tried your answer but unfortunately, for `UITableViewStyleGrouped` the table cells have extra margins that change their alignment with the header. This is a problem when you actually want to depict a multi-column table and use the header to show column titles (and then have the individual table entries align with these titles). – brainjam Sep 29 '11 at 19:17
11

Select Grouped TableView style

Select Grouped Table View style from your tableView's Attribute Inspector in storyboard.

Arjun Shukla
  • 337
  • 7
  • 10
5

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. Hope this small change will help you ..

Ash
  • 5,525
  • 1
  • 40
  • 34
5

Set the headerView of the table with a transparent view with the same height of the header in section view. Also initi the tableview with a y frame at -height.

self.tableview = [[UITableView alloc] initWithFrame:CGRectMake(0, - height, 300, 400)];

UIView *headerView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)] autorelease];
[self.tableView setTableHeaderView:headerView];
beggs
  • 4,185
  • 2
  • 30
  • 30
cescofry
  • 3,706
  • 3
  • 26
  • 22
4

I found an alternative solution, use the first cell of each section instead a real header section, this solution don't appears so clean, but works so fine, you can use a defined prototype cell for your headers section, and in the method cellForRowAtIndexPath ask for the indexPath.row==0, if true, use the header section prototype cell, else use your default prototype cell.

AlexBB
  • 41
  • 1
  • If you still want to work with `indexPath.row` & `indexPath.section`, make sure you return a height of `0.0f` for `- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section` so your headers are invisible. – Soup Nov 05 '12 at 23:33
2

Now that the grouped style looks basically the same as the plain style in iOS 7 (in terms of flatness and background), for us the best and easiest (i.e. least hacky) fix was to simply change the table view's style to grouped. Jacking with contentInsets was always a problem when we integrated a scroll-away nav bar at the top. With a grouped table view style, it looks exactly the same (with our cells) and the section headers stay fixed. No scrolling weirdness.

Greg Combs
  • 4,252
  • 3
  • 33
  • 47
  • This is the best solution especially if the cells are custom, it seems to simply stop the "hold" of the section header when the scroll hits the top. – Recycled Steel Feb 18 '15 at 15:43
1

Assign a negative inset to your tableView. If you have 22px high section headers, and you don't want them to be sticky, right after you reloadData add:

self.tableView.contentInset = UIEdgeInsetsMake(-22, 0, 0, 0); 
self.tableView.contentSize = CGSizeMake(self.tableView.contentSize.width, self.tableView.contentSize.height+22); 

Works like a charm for me. Works for section footers as well, just assign the negative inset on the bottom instead.

Mike A
  • 2,501
  • 2
  • 23
  • 30
0

Check my answer here. This is the easiest way to implement the non-floating section headers without any hacks.

Community
  • 1
  • 1
anemo
  • 1,327
  • 1
  • 14
  • 28
0

@LocoMike's answer best fitted my tableView, however it broke when using footers as well. So, this is the corrected solution when using headers and footers:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return (self.sections.count + 1) * 3;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (section % 3 != 1) {
        return 0;
    }
    section = section / 3;
    ...
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if (section % 3 != 0) {
        return nil;
    }
    section = section / 3;
    ...
}

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

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    if (section % 3 != 2) {
        return 0;
    }
    section = section / 3;
    ...
}

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

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    if (section % 3 != 2) {
        return nil;
    }
    section = section / 3;
    ...
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    int section = indexPath.section;
    section = section / 3;
    ...
}
marmor
  • 27,641
  • 11
  • 107
  • 150
0

I add the table to a Scroll View and that seems to work well.

Dolbex
  • 133
  • 7
0

Swift version of @awulf answer, which works great!

func scrollViewDidScroll(scrollView: UIScrollView) {
    let sectionHeight: CGFloat = 80
    if scrollView.contentOffset.y <= sectionHeight {
        scrollView.contentInset = UIEdgeInsetsMake( -scrollView.contentOffset.y, 0, 0, 0)
    }else if scrollView.contentOffset.y >= sectionHeight {
        scrollView.contentInset = UIEdgeInsetsMake(-sectionHeight, 0, 0, 0)
    }
}
Jeff
  • 840
  • 10
  • 29
-2

I've learned that just setting the tableHeaderView property does it, i.e. :

 tableView.tableHeaderView = customView;

and that's it.

Soni
  • 454
  • 8
  • 23