Ok, I did something similar to what the music app and search do in the spotlight.
I did not subclass UITableView, I just traced the sections using the scrollViewDidScroll method and added the headers to the left of the tableView (so you will need to put the viewview to the right in the view view view view, which means you cannot use the UITableViewController).
this method should be called in scrollViewDidScroll, ViewDidLoad and in didRotateFromInterfaceOrientation: (if you support rotation)
Keep in mind that you will need to make the space on the left equal to the size of the headers in any orientation of the interface that you support.
-(void)updateHeadersLocation { for (int sectionNumber = 0; sectionNumber != [self numberOfSectionsInTableView:self.tableView]; sectionNumber++) { // get the rect of the section from the tableview, and convert it to it superview coordinates CGRect rect = [self.tableView convertRect:[self.tableView rectForSection:sectionNumber] toView:[self.tableView superview]]; // get the intersection between the section rect and the view rect, this will help in knowing what portion of the section is showing CGRect intersection = CGRectIntersection(rect, self.tableView.frame); CGRect viewFrame = CGRectZero; // we will start off with zero viewFrame.size = [self headerSize]; // let set the size viewFrame.origin.x = [self headerXOrigin]; /* three cases: 1. the section origin is still showing -> header view will follow the origin 2. the section origin isn't showing but some part of the section still shows -> header view will stick to the top 3. the part of the section that showing is not sufficient for the view height -> will move the header view up */ if (rect.origin.y >= self.tableView.frame.origin.y) { // case 1 viewFrame.origin.y = rect.origin.y; } else { if (intersection.size.height >= viewFrame.size.height) { // case 2 viewFrame.origin.y = self.tableView.frame.origin.y; } else { // case 3 viewFrame.origin.y = self.tableView.frame.origin.y + intersection.size.height - viewFrame.size.height; } } UIView* view = [self.headerViewsDictionary objectForKey:[NSString stringWithFormat:@"%i", sectionNumber]]; // check if the header view is needed if (intersection.size.height == 0) { // not needed, remove it if (view) { [view removeFromSuperview]; [self.headerViewsDictionary removeObjectForKey:[NSString stringWithFormat:@"%i", sectionNumber]]; view = nil; } } else if(!view) { // needed, but not available, create it and add it as a subview view = [self headerViewForSection:sectionNumber]; if (!self.headerViewsDictionary && view) self.headerViewsDictionary = [NSMutableDictionary dictionary]; if (view) { [self.headerViewsDictionary setValue:view forKey:[NSString stringWithFormat:@"%i", sectionNumber]]; [self.view addSubview:view]; } } [view setFrame:viewFrame]; } }
we also need to declare a property that will retain visible views:
@property (nonatomic, strong) NSMutableDictionary* headerViewsDictionary;
these methods return the size and offset of the X axis of the header representations:
-(CGSize)headerSize { return CGSizeMake(44.0f, 44.0f); } -(CGFloat)headerXOrigin { return 10.0f; }
I created the code so that all the headers that are not needed are deleted, so we need a method that would return a view if necessary:
-(UIView*)headerViewForSection:(NSInteger)index { UIImageView* view = [[UIImageView alloc] init]; if (index % 2) { [view setImage:[UIImage imageNamed:@"call"]]; } else { [view setImage:[UIImage imageNamed:@"mail"]]; } return view; }
this is how it will look:


How it will look in lanscape, I used contraindications to give 44px on the left side of the View table
hope this helps :).