UISearchBar jumps if pressed controller hides tab bar

My user interface structure is as follows:

UITabBarController ( TBC ) -> UINavigationController ( NC ) UITableViewController ( TVC )

(for simplicity, we can say that TBC has only one controller in its viewControllers array - NC )

The My TVC has UISearchBar as the title of the table, and when a TVC I hide the search bar under the navigation panel on the NC shift of the contents of the table view settings.

When the user clicks a cell in the TVC , another view controller ( VC ) is pressed and hides the tab bar with VC.hidesBottomBarWhenPushed = YES;

Now there is a very annoying behavior that I do not know how to solve:
When the user presses the back button from VC to TVC , the search bar enters visibility mode even if it was hidden (under the navigation bar) to VC .

This effect occurs only if the TVC does not have enough lines to fill the screen, it, like the search bar, forces itself to be visible if there is space on the screen. but actually it looks bad and buggy.

I downloaded a simple project that demonstrates the problem and has the same structure as in my question.
I added two buttons for your convenience, the "Hide panel" button hides the search bar for you, and the "toggle count" button toggles the counting of table rows to demonstrate that the problem only occurs if there are several elements.

+6
source share
12 answers

Well .. It seems to me that you came across a mistake. This should be reported via apples bugreporter ( here ).

I did a simple practical work, but remember what works . This will work, but you may have to view it if you have / add other controls to the tableView. It should be safe to use (not acting randomly), and it is not the ugliest of jobs, so I think it’s normal to use in the release. I downloaded the same project with the fix here , and you can just go and download it and you will probably understand what I did. I will explain (in detail) what I actually thought and did here, in case the download links will die in the future:

Train of thought:

As simalone also said, the problem is that when hidesBottomBarWhenPushed set to YES , then it is called by an additional viewDidLayoutSubviews , which somehow resets the current state. We need to redefine viewDidLayoutSubviews and check if we put subviews, because we come from ViewController , or if it is a normal call. When we establish that the call is valid because we are returning from the ViewController , we need to hide the search bar (only if it was hidden earlier).

When we return from the ViewController , three calls are made to the viewDidLayoutSubviews in the TableViewController . I assume the first one is for the tableView , and it seems that the second call is the "for" (or rather, out of) the tabBar. This second is the one that moves the searchBar down. I do not know what the third challenge is, but we can ignore it.

So, now there are three things that need to be checked inside viewDidLayoutSubviews : we need to check if we are returning from the ViewController , we need to check if the SearchBar is hidden before we click (if it should now hide), and we need to check that this is the second call for this method.

Primarily.

In TableViewController I added the @property BOOL backPush; in the header ( .h ) is the file. Now I need to change this variable from ViewController .

In the ViewController , I set this:

 #import "TableViewController" ... -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if(self.isMovingFromParentViewController) { if([self.navigationController.topViewController isKindOfClass:[TableViewController class]]) [((TableViewController*)self.navigationController.topViewController) setBackPush:YES]; } } 

In the above code, when the view disappears (IE pushing forward, backward, closing, whatever), I check if we disappear because it has been removed from the parent. If so (this is when the back button is called), I check to see if the top-level controller now has the current class of the TableViewController class, which also exists if we return. Then I set the backPush property to YES . This is the only thing we need in the ViewController .

Now, in the TableViewController . I added a counter next to your row count:

 @interface TableViewController () { NSInteger _rows; int count; } 

This is to keep track of how many calls were made to viewDidLayoutSubviews later. I set count = 0; in viewDidLoad .

Now to the magic:

 -(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if((self.backPush && count == 0 && self.tableView.contentOffset.y == self.tableView.tableHeaderView.frame.size.height) || (self.backPush && count == 1 && self.tableView.contentOffset.y == 0)) { if(count == 0) count++; else { count = 0; self.backPush = NO; [self hideSearchBar]; } } else if((count == 0 || count == 1) || self.tableView.tableHeaderView.isFirstResponder) { count = 0; self.backPush = NO; } } 

The first if statement wants any of these situations:

  • backPush YES , count is 0 , and searchBar is already hidden.
  • backPush is YES , count is 1 , and the search bar is visible.

If 1. is true, then we increase count by 1. If 2. is true, then 1. has already happened, and now we know that we are in the second round of viewDidLayout.. when we return from VC And that searchBar WAS is hidden (because happened 1.), but now is not hidden. This probably happens in a super-method or something like that. Now we can finally click SearchBar again. I also reset the count and set backPush back to NO .

else if also very important. It checks if there is count 0 or 1 , or if the keyboard is displayed in the search bar. If count is 0 or 1 when it reaches here, it means that the first if statement was hit, for example, that searchBar was not hidden or that it was scrolling far up.

(When I think about it, the else-if should check if backPush YES . Now it sets these variables many times)

Let me know if you find a better way!

+7
source

I think this is a simple solution. Thanks Sti

to give some ideas to solve this problem.

Initialize var hideSearchBar = false variable var hideSearchBar = false

and inside viewDidLayoutSubviews add this code to support the same offset content.

 if hideSearchBar == true { self.tableView.contentOffset = CGPointMake(0, self.tableView.tableHeaderView!.bounds.height - self.tableView.contentInset.top) } 

Finally, implement the methods below.

 override func scrollViewDidScroll(scrollView: UIScrollView) { if self.tableView.tableHeaderView!.bounds.height - self.tableView.contentInset.top == self.tableView.contentOffset.y && self.tableView.dragging == false { hideSearchBar = true } else if self.tableView.dragging == true {//Reset hiding process after user dragging hideSearchBar = false } } func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if self.tableView.contentOffset.y + self.tableView.contentInset.top <= self.tableView.tableHeaderView!.bounds.height { self.tableView.contentOffset = CGPointMake(0, self.tableView.tableHeaderView!.bounds.height - self.tableView.contentInset.top) } } 
+2
source

Try installing for TVC

  self.automaticallyAdjustsScrollViewInsets = NO 
+1
source

This is a problem caused by hidesBottomBarWhenPushed = YES , if you uncheck the Hide Bottom Bar On Push check box, the search bar will not appear when VC returns to TVC.

Try this in TableViewController.m:

 - (void)viewDidLayoutSubviews{ [super viewDidLayoutSubviews]; [self hideSearchBar]; } 

I can’t explain why, but I know that if hidesBottomBarWhenPushed = YES for UITabBarController for push vc, viewDidLayoutSubviews will be called more than once when the view appears again. Initial real-time views keep the same position, while the second time it is called, subviews will be configured for some reason to relay with the most original position, which is very strange. Your custom layout in viewDidLayoutSubviews prevent this even after viewDidAppear .

0
source

The UITableViewController always changes its content offset to UITableviews in its viewDidAppear to make sure all its rows are visible. Therefore, your hacker methods do not work here.

There are several solutions to this problem. Below I chose the one that I selected

First remove this searchBar from the storyboard.

  @interface TableViewController () {
     NSInteger _rows;
 }

 @end

 @implementation TableViewController

 - (id) initWithStyle: (UITableViewStyle) style
 {
     self = [super initWithStyle: style];
     if (self) {
         // Custom initialization
     }
     return self;
 }

 - (void) viewDidLoad
 {
     [super viewDidLoad];

     _rows = 4;  // +1 for searchBar

 }

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

 - (void) hideSearchBar {
     // hide search bar
     [[self tableView] scrollToRowAtIndexPath: [NSIndexPath indexPathWithIndex: 1] atScrollPosition: UITableViewScrollPositionTop animated: NO];
 }

 - (IBAction) toggleCount: (UIBarButtonItem *) sender {
     if (_rows == 20) {
         _rows = 4;
     } else {
         _rows = 20;
     }
     [self.tableView reloadData];
 }

 - (IBAction) hideBar: (UIBarButtonItem *) sender {
     [self hideSearchBar];
 }

 #pragma mark - Table view data source

 - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
 {
     // Return the number of sections.
     return 1;
 }

 - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section
 {
     // Return the number of rows in the section.
     return _rows;
 }

 - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
 {

     if (indexPath.row == 0) {
         UITableViewCell * cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: nil];
         UISearchBar * searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake (0, 0, tableView.frame.size.width, 44)];
         [cell addSubview: searchBar];
         return cell;
     }

     static NSString * CellIdentifier = @ "Cell";
     UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath: indexPath];

     // Configure the cell ...
     cell.textLabel.text = @ "cell";

     return cell;
 }

 @end

The above solution simply ensures that the auto scroll mask is disabled.

If you want your searchBar to be hidden by default, override the UITableView and call hideSearchBar when the tableview is initially loaded for the first time.

0
source

My decision is a little stupid.

Add this method to the sample code.

 - (void)viewWillLayoutSubviews { [self hideSearchBar]; } 

It seems the tableView will redraw the scrollView inside it.

0
source

Since tableView reset contentOffset, I created my own tableView to preserve the hidden status of the search bar. Below is the code. Hope this helps.

 // // TableViewController.m // SearchBarJump // // Created by Eyal Cohen on 3/9/14. // Copyright (c) 2014 Eyal. All rights reserved. // #import "TableViewController.h" @interface CustomTableView : UITableView @property (nonatomic, assign, getter = isSearchBarHidden)BOOL searchBarHidden; @end @implementation CustomTableView @synthesize searchBarHidden = _searchBarHidden; - (void)layoutSubviews { [super layoutSubviews]; if (self.isSearchBarHidden) { [self hideSearchBar:NO]; } } - (void)setSearchBarHidden:(BOOL)searchBarHidden { _searchBarHidden = searchBarHidden; if (_searchBarHidden && self.contentOffset.y != self.tableHeaderView.frame.size.height) { [self hideSearchBar:YES]; } } - (void)hideSearchBar:(BOOL)animated { // hide search bar [self setContentOffset:CGPointMake(0.0, self.tableHeaderView.frame.size.height) animated:animated]; } @end @interface TableViewController () { NSInteger _rows; } @property (nonatomic, weak) IBOutlet CustomTableView *mainTable; @end @implementation TableViewController @synthesize mainTable = _mainTable; - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view = _mainTable; [_mainTable setDelegate:self]; [_mainTable setDataSource:self]; _rows = 3; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.mainTable setSearchBarHidden:YES]; } - (void)hideSearchBar { // hide search bar [_mainTable setContentOffset:CGPointMake(0.0, self.tableView.tableHeaderView.frame.size.height) animated:NO]; } - (IBAction)toggleCount:(UIBarButtonItem *)sender { if (_rows == 20) { _rows = 3; } else { _rows = 20; } [_mainTable reloadData]; } - (IBAction)hideBar:(UIBarButtonItem *)sender { [self hideSearchBar]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return _rows; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // Configure the cell... cell.textLabel.text = @"cell"; return cell; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [_mainTable setSearchBarHidden:NO]; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if (_mainTable.contentOffset.y == _mainTable.tableHeaderView.bounds.size.height) { [_mainTable setSearchBarHidden:YES]; } } @end 
0
source

Bug fixed:

 @interface NTTableView : UITableView @end @implementation NTTableView -(void)setContentOffset:(CGPoint)contentOffset{ if (self.contentOffset.y==-20&& contentOffset.y==-64) { NSLog(@"iOS7 bug here, FML"); }else{ [super setContentOffset:contentOffset]; } } @end 
0
source

Correct my somewhat similar situation with UISearchBar as tableHeaderView . Not sure if this falls into the same exact scenario, but hides the search bar when the view appears. (Without worrying about the number of rows in the table view)

 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; } 
0
source

Setting edgesForExtendedLayout to [.top, .bottom] instead of .top for TVC for me.

Of course automaticallyAdjustsScrollViewInsets set to false
EDIT: It seems to work only if tvc.tabBar translucent

0
source

As a strange hack, I can only suggest adding an empty cell to the end of cells with a height of about 400

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return _rows + 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; if(indexPath.row == _rows) { //cellEmpty - cell identifier in storyboard cell = [tableView dequeueReusableCellWithIdentifier:@"cellEmpty" forIndexPath:indexPath]; } else { cell.textLabel.text = @"cell"; } // Configure the cell... return cell; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.row == _rows) { return 400; } else { return 44; } } 
-1
source

your output file

https://github.com/iDevAndroid/SearchBarJump

just use this code, don't make it complicated for this

 -(void)viewDidDisappear:(BOOL)animated{ [self.tableView setContentInset:UIEdgeInsetsMake(-0.3, 0, 0, 0)]; [super viewDidAppear:animated]; } 

there is one problem here if you are set to UIEdgeInsetsMake (0, 0, 0, 0), as in the original mode, jump searchBar

-1
source

All Articles