如果推送控制器隐藏标签栏,UISearchBar 会跳转

Posted

技术标签:

【中文标题】如果推送控制器隐藏标签栏,UISearchBar 会跳转【英文标题】:UISearchBar jumps if pushed controller hides the tab bar 【发布时间】:2014-03-06 18:08:14 【问题描述】:

我的 UI 结构如下:

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

(为简单起见,假设 TBC 在其viewControllers 阵列上只有一个控制器 - NC

我的 TVCUISearchBar 作为其表头,当 TVC 出现时,我将搜索栏隐藏在 NC 导航栏下方通过设置表视图内容偏移量。

当用户点击 TVC 中的一个单元格时,会推送另一个视图控制器 (VC) 并使用 VC.hidesBottomBarWhenPushed = YES; 隐藏标签栏

现在有一个很烦人的行为我不知道如何解决: 当用户从 VC 点按返回按钮回到 TVC 时,搜索栏会跳转到可见,即使它在 之前隐藏(在导航栏下方) VC 被推送。

这种效果只有在 TVC 没有足够的行来填满屏幕时才会发生,就像如果屏幕上有一个位置,搜索栏会强制自己可见。但它真的看起来很糟糕而且有问题。

我上传了一个 simple project 来演示该问题,它与我在问题中描述的结构相同。 为方便起见,我添加了两个栏按钮,“隐藏栏”按钮为您隐藏搜索栏,“切换计数”按钮切换表格视图的行数以证明问题仅在项目很少时才会发生。

【问题讨论】:

视频/示例项目会有所帮助(我知道重新创建很简单,但可以帮助缩小可能的解决方案) @staticVoidMan 我用一个示例项目更新了我的问题。 hm... 确实很奇怪,当您选择 searchBar 并且当键盘在屏幕上并且您按下并弹回时,搜索栏会变得更低。 ...让c @Eyal 你的问题解决了,检查我的解决方案 简单的逻辑为什么你会打破你的脑袋 【参考方案1】:

好的.. 在我看来你偶然发现了一个错误。应该通过 apples bugreporter (here) 报告。

我做了一个简单的变通办法,但请记住,它变通办法。这将起作用,但如果您有/向 tableView 添加其他控件,您可能需要查看它。它应该是安全的(不是随机行为),而且它不是最丑陋的变通方法,所以我认为在发布中使用它是可以的。我已经上传了相同的项目,修复了here,你可以继续下载它,你可能会明白我做了什么。我将(非常详细地)解释我在这里的实际想法和做法,以防下载链接将来失效:

思路:

正如 simalone 所说,问题是当 hidesBottomBarWhenPushed 设置为 YES 时,它会调用一个额外的 viewDidLayoutSubviews 以某种方式重置您当前的状态。我们需要覆盖viewDidLayoutSubviews,并检查我们是否正在布置子视图因为我们来自ViewController,或者这只是一个常规调用。当我们确定调用确实是因为我们从ViewController 返回时,我们需要隐藏搜索栏(仅当它之前隐藏时)。

当我们从ViewController 返回时,在TableViewController 中对viewDidLayoutSubviews 进行了三个调用。我猜第一个是为tableView,似乎第二个调用是'for'(或者更确切地说是来自)tabBar。这第二个是将searchBar 向下移动的那个。我不知道第三个电话是什么,但我们可以忽略它。

所以现在我们需要在viewDidLayoutSubviews 中检查三件事:我们需要检查我们是否从ViewController 返回,我们需要检查 searchBar 在我们推送之前是否隐藏(如果应该现在隐藏),我们需要检查它是否是对这个方法的第二次调用。

首先要做的事情。

TableViewController 中,我在标题(.h) 文件中添加了一个属性@property BOOL backPush;。现在我需要从ViewController 更改这个变量。

ViewController,我放了这个:

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

在上面的代码中,当视图消失(即向前、向后、关闭等等)时,我正在检查我们是否因为它已从父视图中删除而消失。如果是(调用后退按钮时),我检查 now-current 顶视图控制器是否属于 TableViewController 类,如果我们返回,它也是。然后我将属性backPush 设置为YES。这是我们在ViewController 中唯一需要的。

现在,到TableViewController。我在您的行数旁边添加了一个计数器

@interface TableViewController () 
    NSInteger _rows;
    int count;

这是为了跟踪以后有多少次调用viewDidLayoutSubviews。我在viewDidLoad 中设置了count = 0;

现在开始魔术:

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

第一个 if 语句需要以下任何一种情况:

    backPushYEScount0,并且 searchBar 已经隐藏。 backPushYEScount1,并且 searchBar 是可见的。

如果 1. 为真,那么我们将 count 加 1。 如果 2. 是真的,那么 1. 已经发生了,我们现在知道当我们从 VC 回来时我们正处于第二轮 viewDidLayout.. 并且 searchBar WAS 隐藏(因为 1. 发生了)但现在没有隐藏。它可能发生在超级方法或其他东西中。 现在我们终于可以再次推出 searchBar 了。我还重置了计数并将 backPush 设置回NO

else if 也很重要。它检查count0 还是1,或者搜索栏是否显示键盘。如果 count 到达这里时是 01,则意味着第一个 if 语句失败,例如 searchBar 没有隐藏,或者它被向上滚动得很远。

(当我想到它时,else-if 应该检查backPush 是否也是YES。现在它重复设置这些变量)

如果你找到更好的方法,请告诉我!

【讨论】:

感谢您的详细回答,但这看起来是一个非常大的解决方法,所以我现在更愿意避免它,为您的努力 +1 @Eyal 我同意,但问题似乎是一个错误,所以这是我能想到的临时修复的唯一方法。您应该将其报告给 Apple 的 bugreporter。还是您找到了其他解决方案?【参考方案2】:

我认为这是一个简单的解决方案。谢谢 Sti

提供一些解决此错误的想法。

初始化变量var hideSearchBar = false

并在 viewDidLayoutSubviews 中添加此代码以保持相同的内容偏移量。

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

最后实现以下方法。

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)
    

【讨论】:

【参考方案3】:

尝试设置为 TVC

 self.automaticallyAdjustsScrollViewInsets = NO

【讨论】:

【参考方案4】:

这是hidesBottomBarWhenPushed = YES引起的问题,如果不勾选Hide Bottom Bar On Push,VC回TVC时不会出现searchBar。

在 TableViewController.m 中试试这个:

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

我无法解释为什么,但我知道如果 hidesBottomBarWhenPushed = YES 让 UITabBarController 推送 vc,当视图再次出现时,viewDidLayoutSubviews 将被多次调用。第一次子视图保持相同的位置,而第二次被调用时,子视图会由于某种原因被调整到最原始的位置,这很奇怪。即使在viewDidAppear 之后,在viewDidLayoutSubviews 中进行自定义布局也可以防止这种情况发生。

【讨论】:

这将阻止用户完全显示搜索栏 @Eyal 你应该做什么viewDidLayoutSubviews 取决于你自己。这里只是一个如何控制搜索栏外观的例子,您可以将外观保存在viewDidDisappear,然后在viewDidAppear之后恢复到viewDidLayoutSubviews【参考方案5】:

我的解决方案有点愚蠢。

将此方法添加到示例代码中。

- (void)viewWillLayoutSubviews

    [self hideSearchBar];

好像tableView会重绘里面的scrollView。

【讨论】:

【参考方案6】:

由于tableView重置了contentOffset,所以我做了一个自定义tableView有属性来保存搜索栏的隐藏状态。下面是代码。希望对你有帮助。

//
//  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

【讨论】:

【参考方案7】:

UITableViewController 总是在其 viewDidAppear 中修改其 UITableviews 内容偏移量,以确保其所有行都是可见的。所以你的hacky方法在这里不起作用。

这个问题有几种解决方案。我选择的如下图所示

首先从情节提要中删除该搜索栏。

@interface TableViewController () NSInteger _rows; @结尾 @implementation 表视图控制器 - (id)initWithStyle:(UITableViewStyle)style self = [超级 initWithStyle:style]; 如果(自我) // 自定义初始化 回归自我; - (void)viewDidLoad [超级视图DidLoad]; _rows = 4; // +1 搜索栏 -(void)viewDidAppear:(BOOL)动画 [超级 viewDidAppear:动画]; - (void)hideSearchBar //隐藏搜索栏 [[self tableView] scrollToRowAtIndexPath:[NSIndexPath indexPathWithIndex:1] atScrollPosition:UITableViewScrollPositionTop 动画:NO]; - (IBAction)toggleCount:(UIBarButtonItem *)sender 如果(_rows == 20) _rows = 4; 别的 _rows = 20; [self.tableView reloadData]; - (IBAction)hideBar:(UIBarButtonItem *)sender [自我隐藏搜索栏]; #pragma mark - 表格视图数据源 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView // 返回部分的数量。 返回 1; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section // 返回节中的行数。 返回_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)]; [单元格添加子视图:搜索栏]; 返回单元格; 静态 NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // 配置单元格... 单元格.textLabel.text = @"单元格"; 返回单元格; @结尾

上述解决方案只是确保禁用自动滚动魔法。

如果您希望隐藏默认搜索栏,请覆盖 UITableView 并在第一次初始加载 tableview 时调用 hideSearchBar。

【讨论】:

sorry你错了,搜索栏是table view header view,你可以直接打印self.tableView.tableHeaderView看看。 tableHeadrView 也是 tableView 的一个子视图,所以当然你会在打印所有子视图时看到它......无论如何我不喜欢使用你的方法,因为在这里将搜索栏添加到单元格不是一个理想的解决方案。跨度> @Eyal 你是对的。但是,我的解决方案很简单,理想与否是你的选择。【参考方案8】:

我就是这样修复错误的:

@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

【讨论】:

【参考方案9】:

UISearchBartableHeaderView 修复我有点类似的情况。不确定这是否属于相同的确切场景,但是当视图出现时它会隐藏搜索栏。 (不关心表格视图中的行数)

- (void)viewWillAppear:(BOOL)animated

    [super viewWillAppear:animated];
    [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];

【讨论】:

【参考方案10】:

TVC 上将 edgesForExtendedLayout 设置为 [.top, .bottom] 而不仅仅是 .top 为我解决了问题。

当然,automaticallyAdjustsScrollViewInsets 设置为 false编辑: 似乎只有 tvc.tabBar 为 translucent 时才有效

【讨论】:

【参考方案11】:

作为一个奇怪的 hack,我只能建议在高度约为 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;


【讨论】:

【参考方案12】:

你的输出文件

https://github.com/iDevandroid/SearchBarJump

只需使用此代码,不要为此做复杂的事情

-(void)viewDidDisappear:(BOOL)animated

    [self.tableView setContentInset:UIEdgeInsetsMake(-0.3, 0, 0, 0)];

    [super viewDidAppear:animated];

如果你设置 UIEdgeInsetsMake(0, 0, 0, 0) 会出现一个问题,搜索栏会像原始模式一样跳跃

【讨论】:

以上是关于如果推送控制器隐藏标签栏,UISearchBar 会跳转的主要内容,如果未能解决你的问题,请参考以下文章

在导航控制器中推送控制器时如何隐藏父标签栏

隐藏标签栏控制器

放松Segue隐藏标签栏

隐藏在标签栏控制器标签栏旁边的 iPhone 视图

在导航控制器中添加标签栏控制器时隐藏后退按钮

推送视图控制器后带回标签栏?