ChatGPT解决这个技术问题 Extra ChatGPT

iPhone: Hide UITableView search bar by default

I used the Interface Builder to create a table view, to which I added the library's Search Bar and Search Display Controller to add search functionality. However, IB set it up so that the bar is visible at the top of the screen when the view is first displayed.

I'd like to know how to have the search bar be hidden by default but still scrollable with the table view (see Apple's Mail application for an example). I've tried calling scrollRectToVisible:animated: in viewDidLoad to scroll the table view down, but to no avail. What's the preferred way of hiding the search bar by default?


e
eonil

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)).


Actually, I'm getting an issue where the table view won't scroll if there aren't enough rows to fill the screen - in a table with one or two rows, this code does nothing. Is that expected behavior? Can I get around it somehow?
I found a way: instead of scrolling to an index path, change the content offset of the table's scroll view to a CGRect at 0.0, 44.0
Tim, that's the way to go! I used self.tableView.contentOffset = CGPointMake(0, self.searchDisplayController.searchBar.frame.size.height);
Btw - when I was putting in this code after a call to [tableView reloadData] which caused my table to go from 2 rows to enough rows to allow normal scrolling, I found that using the contentOffset method caused a jerky showing and hiding of the search box while using the scrollToRowAtIndexPath method didn't have the same jerkiness.
This is the only ultimate way which works under ANY circumstances. Setting contentOffset with whatever value will break if you're employing complex view-controller layout. I don't recommend it.
J
Jingshao Chen

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];
}

For me, this was the only solution that produced a hidden UISearchBar implementation that felt like it was in-line with the HIG. The other solutions where it's "stuck" to the top of the table view feel unnatural.
This solution worked the best. This should be the answer in my opinion.
This solution is nice(+1). However, tableView doesn't hide the search bar properly in some cases(in my case: when the tableview controller is pushed back from another view controller). There is a question about this issue: stackoverflow.com/questions/15222186/… I think the provided answer of this question is not quite good though. @bandejapaisa, do you have any idea?
This worked well for me, though instead of hard-coding 44 throughout I suggest using self.tableView.tableHeaderView.frame.size.height in viewWillAppear to allow for changes Apple may make in the future to the search bar height.
putting this in viewWillAppear is a bad idea because it'll run everytime you navigate back to your view. This means your tableview will slowly inch down. Irritating bug!
S
Sam Spencer

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];


I've found that the animation is the key to this solution. Trying to set the content offset without the animation will appear to do nothing. Rather than use the animation methods (or blocks) in this sample however, I find that simply calling [self.tableView setContentOffset:CGPointMake(0,44) animated:TRUE] works well enough.
It seems to not work at all without the animation. @quickthyme has made the same observation.
I've had success with this without needing the animations if you implement it in viewDidAppear: instead of viewDidLoad.
M
Matej

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


this is the only one that worked for me! , but it only does it un viewdidload... so first time its hiden, but then is not hiden anymore while de app is running. And i tried putting it in viewwillappear and viewdidappear but works wrong. Thanks!
@fguespe that's strange.. The viewDidLoad should get called every time the view is instantiated. It never happened to me that the UISearchBar was visible after coming back from another view.
im using a tabbed application. Maybe that?
@fguespe I see that could be the issue hmm. Unfortunately im not sure how you'd go about solving that if -viewWillAppear doesn't work.. Does it get called when the tab controller switches to the view?
viewwillappear and viewdidappear are called , but i tried with both and the view is like wrong offseted. It doesnt return the expected result. I mean, something does, but it does it bad.
D
Dasoga

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)

k
kbpontius

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()

B
Brian

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.


I had to do this for iOS 11. Setting contentOffset alone worked for iOS 9/10 for 0 or more cells, and it also worked on iOS 11 for 8+ cells. Anything less required this solution. Is there a reason why?
I think the first paragraph of this post explains why.
I understood the first paragraph, but it was still strange that it only was "broken" on iOS 11 for me. I would have expected similar behavior for iOS 9, 10, and 11 when the number of cells are the same (less than enough). Thanks again and sorry for bringing up an old post!
g
grep

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


You should really not hard code your points, why don't you use CGRectGetHeight(searchBar.bounds)?
I guess you must be doing it in viewDidLoad() / init() where no frame has been set for you to get the height of the search bar. May be you should try with viewDidLayoutSubviews()
If the tableView has not finished loading, none of the ContentOffset solutions will work
R
RyanJM

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.


Thank you! This was what I was looking for. You need to go way up.
d
dibi

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)];
}

p
p0lAris

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};
}

Good solution for the offset problem. Works for me when using a UINavigationController.
The solution doesn't work when the number of rows does not cover the entire screen - would yo know a workaround for this?
Not sure I understand this completely. This works for me when I have just a few rows in the table. However I do use the following: self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];.
C
Confused Vorlon

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)
    }

C
Chilly Zhong

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 :-)


L
LightMan

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


k
kbpontius

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.


d
drv

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
        }

A
Ashu

Below code is working for me

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

A
Abdur Rahman

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


f
fujianjin6471

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.


s
stakahop

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)

J
Johannes Knust

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)
}

s
slobodans

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"];
}