IPhone: 默认情况下隐藏 UITableView 搜索栏

我使用这个 Interface Builder 创建了一个表视图,并在其中添加了图书馆的搜索栏和搜索显示控制器来添加搜索功能。但是,IB 将其设置为当视图第一次显示时,可以在屏幕顶部看到该条。

我想知道如何隐藏搜索栏默认情况下,但仍然可以滚动的表格视图(见苹果的邮件应用程序的例子)。我尝试在 viewDidLoad中调用 scrollRectToVisible:animated:来向下滚动表视图,但是没有用。默认情况下隐藏搜索栏的首选方法是什么?

74528 次浏览

First make sure, to add the UISearchBar to the tableHeaderView of the UITableView so that it gets scrolled with the table's content and isn't fixed to the top of the view.

The searchbar isn't counted as a row in the tableview, so if you scroll the top of the tableview to the first row, it 'hides' the searchbar:

[yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];

or in Swift:

yourTableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)

Make sure to not scroll the tableview before it contains data (scrollToRowAtIndexPath will raise an exception if the given indexPath does not point to a valid row (i.e. if the tableview is empty)).

I find that the "Notes" app is unable to search items when the tableView is blank. So I think it's just using -(void)addSubview:(UIView *)view to add a blank table at first. When you add an item to its data source array, it will run another peice of code to load tableView.

So my solution is:

if([self.arrayList count] > 0)
{
[self.tableView reloadData];     //use jdandrea's code to scroll view when cell==nil in cellForRowAtIndexPath
self.tableView.scrollEnabled = YES;
}
else
{
tableBlank = [[UITableView alloc]initWithFrame:CGRectMake(0,0,320,416) style:UITableViewStylePlain] autorelease];
tableBlank.delegate = self;
tableBlank.dataSource = self;
[self.view addSubview:tableBlank];
}

Hope this helps :-)

The contentOffset was noted in the comments by Tim and Joe D'Andrea, but this is a bit expanded by adding an animation to hiding the search bar. I noticed it is a bit unexpected for the search bar to just disappear.

A little update for those who want to target iOS 4.0 and above, try this:

[UIView animateWithDuration:0.4 animations:^{
self.tableView.contentOffset = CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height);
} completion:nil];

Previous answer:

[UIView beginAnimations:@"hidesearchbar" context:nil];
[UIView setAnimationDuration:0.4];
[UIView setAnimationBeginsFromCurrentState:YES];


self.tableView.contentOffset = CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height);


[UIView commitAnimations];

Dont' add the UISearchBar as a subview of the UITableView, this isn't necessary.

The UITableView has a tableHeaderView property that is perfect for this:

- (void) viewDidLoad {
[super viewDidLoad];
self.searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)] autorelease];
self.searchBar.showsCancelButton = YES;
self.searchBar.delegate = self;
self.tableView.tableHeaderView = self.searchBar;
}

If you don't want to see it by default:

- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView setContentOffset:CGPointMake(0, 44)];
}

I also get the cancel button to hide it again.... (and remove the keyboard)

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[self.tableView setContentOffset:CGPointMake(0, 44) animated:YES];
[self.searchBar resignFirstResponder];
}

Change the bounds of the tableView, this can be done inside viewDidLoad plus you don't have to worry about if the table is empty or not (to avoid the exception).

CGRect newBounds = self.tableView.bounds;
newBounds.origin.y = newBounds.origin.y + self.searchBar.bounds.size.height;
self.tableView.bounds = newBounds;

Once the user has scrolled down the table the search bar will appear and behave normally

There are many answers to this solution, however none worked very well for me.

This works perfectly. No animation, and is done upon -viewDidLoad

 - (void)viewDidLoad {
[super viewDidLoad];
self.tableView.contentOffset = CGPointMake(0,  self.searchBar.frame.size.height - self.tableView.contentOffset.y);
}

Note:

Code assumes you have the searchBar @property which is linked to the search bar. If not, you can use self.searchDisplayController.searchBar instead

Note that the accepted answer will crash if there is no data in the tableview. And adding it to viewDidLoad may not work under certain circumstances.

[yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; // crashes if no rows/data

Therefore, instead add this line of code:

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];


[yourTableView setContentOffset:CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height)];
}

None of the above worked for me, so just in case if someone will have the same issue.

I have searchBar set as tableHeaderView on a grouped table on iOS7. In viewDidLoad I have tableView.contentOffset = CGPointMake(0, 44); to keep searchBar initially "hidden". When user scrolls down searchBar is dsiplayed

Goal: hide searchBar on cancel button tap

In searchBarCancelButtonClicked(searchBar: UISearchBar!)

This did not work: [yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; tableView was scrolling too much up, resulting in row 0 going behind toolBar.

This did not work: tableView.ContentOffset = CGPointMake(0, 44) - nothing happens

This did not work: tableView.ContentOffset = CGPointMake(0, searchBar.frame.size.height) - nothing happens

This did not work: tableView.setContentOffset(CGPointMake(0, 44), animated: true); Again tableView was scrolling too much up, resulting in row 0 going behind toolBar.

This worked partially: tableView.setContentOffset(CGPointMake(0, 0) but titleForHeaderInSection was going behind toolBar

THIS WORKED: tableView.setContentOffset(CGPointMake(0, -20)

Seems not logical but only -20 moved it up to correct position

Honestly, none of the answers really solved the issue (for me). If your table view has no rows OR the row height is different, these answers don't suffice.

The only thing that works:

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.tableView.contentOffset = (CGPoint){.y =  self.tableView.contentOffset.y + self.searchController.searchBar.frame.size.height};
}

My goal was to hide search bar added to tableHeaderView of plain TableView's style in storyboard when the view controller is loaded and show the bar when user scroll down at the top of UITableView. So, I'm using Swift and has added extension:

extension UITableView {
func hideSearchBar() {
if let bar = self.tableHeaderView as? UISearchBar {
let height = CGRectGetHeight(bar.frame)
let offset = self.contentOffset.y
if offset < height {
self.contentOffset = CGPointMake(0, height)
}
}
}
}

in viewDidLoad() just call:

self.tableView.hideSearchBar()

The other answers to this question either didn't work at all for me, had odd behavior when the tableView had 0 rows, or messed up the scroll position when going back and forth between parent and nested line items. This worked for me (the goal being to hide the UISearchBar when on load):

public override func viewWillAppear(animated: Bool) {
if self.tableView.contentOffset.y == 0 {
self.tableView.contentOffset = CGPoint(x: 0.0, y: self.searchBar.frame.size.height)
}
}

Explanation
Anytime the tableView will appear, this code checks to see if the UISearchBar is visible (meaning the y position of the contentOffset, aka scroll position, is 0). If it is, it simply scrolls down the height of the searchBar. If the searchBar is not visible (think a larger list of items in tableView), then it won't try and scroll the tableView, as this would mess up the positioning when you return from a nested line item.

Side Note
I'd recommend putting this code in a separate method to ensure single responsibility and give you the reusability to call it anytime in your code.

All existing solutions don't work for me on iOS 8 when there are not enough rows to fill the tableView since iOS will adjust the inset automatically in this situation. (Existing answers are good when there are enough rows though)

After wasting like 6 hours on this issue, I finally got this solution.

In short, you need to insert empty cells into the tableView if there are not enough cells, so the content size of the tableView is big enough that iOS won't adjust the inset for you.

Here is how I did it in Swift:

1.) declare a variable minimumCellNum as a class property

var minimumCellNum: Int?

2.) calculate minimumCellNum and set tableView.contentOffset in viewWillAppear

let screenHeight = Int(UIScreen.mainScreen().bounds.height)


// 101 = Height of Status Bar(20) + Height of Navigation Bar(44) + Height of Tab Bar(49)
// you may need to subtract the height of other custom views from the screenHeight. For example, the height of your section headers.
self.minimumCellNum = (screenHeight - 103 - heightOfOtherCustomView) / heightOfYourCell
self.tableView.contentOffset = CGPointMake(0, 44)

3.) in tableView(tableView: UITableView, numberOfRowsInSection section: Int))

let numOfYourRows = YOUR LOGIC
if numOfYourRows > minimumCellNum {
return numOfYourRows
} else {
return minimumCellNum!
}

4.) Register an empty cell, whose selection attribute is None, on the storyboard and in tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)

if indexPath.row < numOfYourRows {
return YOUR CUSTOM CELL
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("EmptyCell", forIndexPath: indexPath) as! UITableViewCell
return cell
}

5.) in tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)

if tableView == self.tableView {
if numOfYourRows < (indexPath.row + 1) {
return
}
YOUR LOGIC OF SELECTING A CELL
}

This is not a perfect solution, but it's the only workaround that really works for me on iOS 8. I'd like to know if there is a neater solution.

If your table will always have at least one row, just scroll to the first row of the table and the search bar will be hidden automatically.

let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)

self.tableView.selectRowAtIndexPath(firstIndexPath, animated: false, scrollPosition: .Top)

If you put the above code on viewDidLoad, it will throw an error because the tableView hasn't loaded yet, so you have to put it in viewDidAppear because by this point the tableView has already loaded.

If you put it on viewDidAppear, everytime you open the tableView it will scroll to the top.

Maybe you don't want this behaviour if the tableView remains open, like when it is a UITabBar View Controller or when you do a segue and then come back. If you just want it to scroll to the top on the initial load, you can create a variable to check if it is an initial load so that it scrolls to the top just once.

Define first a variable called isInitialLoad in the view controller class and set it equal to "true":

var isInitialLoad = true

Then check if isInitialLoad is true on viewDidAppear and if it is true, scroll to the top and set the isInitialLoad variable to false:

if isInitialLoad {
let firstIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.selectRowAtIndexPath(firstIndexPath, animated: false, scrollPosition: .Top)


isInitialLoad = false
}

Content offset is the way to go, but it doesn't work 100% of the time.

It doesn't work if are setting estimatedRowHeight to a fixed number. I don't know the work around yet. I've created a project showing the issue(s) and I've created a radar with Apple.

Check out the project if you want an example in swift of how to set it all up.

Don't forget to take status bar and navigation bar into consideration. My table view controller is embedded in a navigation controller, in viewDidAppear:

tableView.contentOffset = CGPointMake(0, -20) // -20 = 44 (search bar height) - 20 (status bar height) - 44 (navigation bar height)

worked for me.

For Swift:

Just make tableview content y offset, which should be like height of searchbar.

self.tableView.contentOffset = CGPointMake(0, self.searchController.searchBar.frame.size.height)

For Swift 3+

I did this:

Declare this var:

    var searchController = UISearchController()

And in the viewDidLoad() method

    searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = true
searchController.searchBar.placeholder = NSLocalizedString("Search", comment: "")
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar


tableView.contentOffset = CGPoint(x: 0, y: searchController.searchBar.frame.size.height)

Swift 3

works with empty table too!

In my environment I load custom cells by xib file and the rowHeight is automatic set.

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.tableView.contentOffset = CGPoint(x: 0, y: self.tableView.contentOffset.y + self.searchController.searchBar.bounds.height)
}

Below code is working for me

self.tableView.contentOffset = CGPointMake(0.0, 44.0);

I tried setting the content offset and scrollToIndexpath but nothing worked satisfactorily. I removed the setting of tableHeaderView as searchBar from viewDidLoad and set it in scrollview delegate as below

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 && tableView.tableHeaderView == nil {
tableView.tableHeaderView = searchController.searchBar
tableView.setContentOffset(CGPointMake(0, scrollView.contentOffset.y + searchController.searchBar.frame.size.height), animated: false)
}
}

This worked like a charm.The content offset setting is for smooth scrolling purpose

I had similar problem. And only way i found to resolve this problem was to use key value observer on UITableView contentOffset property.

After some debugging i realized that problem appear just if table view content size is smaller than tableview. I start KVO when on viewWillAppear. And dispatch stop KVO 0.3 seconds after view appear. Every time contentOffset became 0.0, KVO react and set contentOffset on search bar height. I stop KVO asynchronous after 0.3 seconds because view layout will reset contentOffset even after view appear.

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}


-(void)viewDidAppear:(BOOL)animated{
__weak typeof (self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf.tableView removeObserver:self forKeyPath:@"contentOffset"];});
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:@"contentOffset"] && self.tableView.contentOffset.y == 0.0) {
self.tableView.contentOffset = CGPointMake(0, CGRectGetHeight(self.tableView.tableHeaderView.frame));
}
}


-(void)dealloc {
[self.tableView removeObserver:self forKeyPath:@"contentOffset"];
}

The scrollToRowAtIndexPath method causes a crash if there are zero rows in the table.

UITableView is just a scroll view though, so you can simply change the content offset.

This checks that you have something as your tableHeaderView, and assuming you do scrolls to hide it

    if let searchHeight = tableView.tableHeaderView?.frame.height {
tableView.setContentOffset(CGPoint.init(x: 0, y:searchHeight ), animated: false)
}