9

I'd like to replicate the sticky behavior of the search bar in the iPhone Contacts app.

Normal stage

When the user scrolls the view down, the search bar also comes down along with the view:

User scrolls the view down

If the user scrolls up, the table scrolls accordingly, with the following two behaviors:

(1) the search bar remains fixed at the top, and
(2) the subsequent section header stops appropriately below the search bar:

User scrolls up

When the next section header comes, the previous header disappears below the search bar:

User scroll up

Note: the section index control (a-z on the right hand side) appears on top of the search bar as well. Ergo, fiddling with the contentInset will push the section index control down along with it.

I've created a custom UIViewController, added a UITableView, set its contentInset to the height of the search bar. I created a UIView, added the search bar as its subview, and then added the UIView to the UITableView. However, as noted above, when the user is scrolling, the section headers still stick at the y-position zero, and not at the header height. Additionally, the section header index control position is adversely affected.

I'd appreciate a solution to this problem.

nevan king
  • 112,709
  • 45
  • 203
  • 241
krisk
  • 6,957
  • 1
  • 18
  • 30
  • Positioning the search bar is the easy part, and you can make the section headers stop at the right place by using the content inset. The hard parts are (1) you have to keep bringing the search bar forward so that new section headers don't cross in front of it, and (2) I can't figure out how to make the index taller to counteract the content inset. I suspect that Apple has access to API that we don't. – matt Feb 08 '13 at 03:02
  • Probably. I should've mentioned initially that I had fiddled with the internal workings of the `UITableView`, and accessed the `UITableViewIndex` and modified its position + behavior. However, that's ill advised, as Apple would most probably reject such blatant abuse of their API – krisk Feb 08 '13 at 17:11
  • I think we have to conclude that there's no way to do what you want to do. There should be! This is a good interface, because it makes the search field easy to find. I recommend submitting a bug report asking for an enhancement. In the meantime, I recommend the interface used by the Mail app on the iPad, where the search bar is not part of the table view. I do know how to achieve that one! :) – matt Feb 08 '13 at 17:55
  • While it's impossible to get the section index control overlap the search bar using public API only, it's pretty straight forward to replicate the behavior of the Contacts app and I agree that this is quite convenient for large lists. :) – Fabian Kreiser Feb 10 '13 at 15:14

2 Answers2

15

It has been quite some work to get all things right, but I just had to prove that it's possible to recreate that behavior, at least almost.
Check out this GitHub project I've created: https://github.com/fabiankr/TableViewSearchBar

Actually, it's not even that complicated:
1) Add the search bar directly to the table view and set the tableView's contentInset
2) In -scrollViewDidScroll: adjust the search bar's frame

There are some caveats you have to take care of, though:
1) When scrolling the table view to the top, the section headers shortly appear above the search bar. In order to solve it, set the search bar's zPosition when scrolling to the top
2) Your content controller needs to be a subclass of UIViewController instead of UITableViewController, because UISearchDisplayController adds the dimming view to the controller's view. Because table view controllers' viewis a table view, the dimming view would be at the wrong position when the table view is scrolled.

One thing that isn't possible using public API only is to make the section index control on the right of the table overlap the search bar. It's only a minor thing and even without it the behavior is very similar to the one found in the Contacts app.

In order to achieve the exact same behavior, you'll have to use private API. There's a method on UITableView called _setPinsTableHeaderView: that needs to be used. The sample project contains implementations for both: 1) public API only and 2) private API to get the section index control overlap the search bar.
Reminder: You shouldn't use private API when you plan to submit the app to the App Store!

Fabian Kreiser
  • 8,307
  • 1
  • 34
  • 60
  • 1
    **+1** for the private method. Let's hope it will become public, this is usefull thing. If someone realy _realy_ wants the index to overlap, just make your own. It's not _so_ difficult. – Tricertops Feb 10 '13 at 20:04
0

The way I achieved this behavior was by detaching my floating cell form the UITableView and making it a subview of the UITableView and animated the floating cell in scrollViewDidScroll. And just so the UITableView scrolls down far enough to reveal the floating cell I also stuck an invisible cell in the tableview which gets covered by the floating cell when scrollViewDidScroll is called.

- (void)viewDidLoad {
   // add floating cell to tableview
   FloatingCell *cell = [[FloatingCell alloc] init];
   [self.tableView addSubview:cell];
   self.floatingCell = cell; 
}
// overwrite scrollViewDidScroll
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGRect floatingCellFrame = floatingCell.frame;
    CGFloat floatingCellHeight = floatingCellFrame.size.height;

    // when contentOffset is is more then cellHeight scroll floating cell
    if (scrollView.contentOffset.y > floatingCellHeight) {
       floatingCellFrame.origin.y = -scrollView.contentOffset.y + floatingCellHeight; 

    // when contentOffset is less then cellHeight stick it to the top of UITableView
    else if (scrollView.contentOffset.y < floatingCellHeight)
       floatingCellFrame.origin.y = 0;

   floatingCell.frame = floatingCellFrame;
}

You might have a to add a couple corner case conditions when scrolling so the floating cell doesn't appear to jump but this should get ya started. Good luck!

Adam Levy
  • 97
  • 2
  • 10
  • This can't be right. The section headers still go to the top of the table; they do not stop below the search bar, as requested by the OP. And if you use the content insets to try to fix that, the section index is too short; it doesn't overlap the search bar as in the Contacts app. Thus, this code fails to solve exactly the issues that the OP has posed. – matt Feb 07 '13 at 21:28
  • You are correct, @matt. The pain points are dealing with the section headers and the section index control. – krisk Feb 07 '13 at 21:41