UISearchController 禁用取消 UIBarButtonItem

Posted

技术标签:

【中文标题】UISearchController 禁用取消 UIBarButtonItem【英文标题】:UISearchController disable cancel UIBarButtonItem 【发布时间】:2015-02-08 16:19:59 【问题描述】:

问题

我正在尝试使用 UISearchController 在地图视图上搜索目的地。我希望 UISearchBar 出现在导航栏中,但如果它不显示右侧的取消按钮,我似乎无法这样做:

这个取消按钮有时会消失,而我正在玩,但我不能让它不出现现在我有搜索表显示我想要它的方式:

我敢肯定我做错了什么小事,但我无法弄清楚它是什么......

我的代码

self.resultsViewController = [UITableViewController new];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsViewController];
self.searchController.searchResultsUpdater = self;
self.searchController.hidesNavigationBarDuringPresentation = false;
self.searchController.delegate = self;
self.searchBar = self.searchController.searchBar;
self.searchBar.placeholder = self.stage.title;
self.searchBar.searchBarStyle = UISearchBarStyleMinimal;

self.definesPresentationContext = true;
self.navigationItem.titleView = self.searchBar;

self.resultsTableView = self.resultsViewController.tableView;
self.resultsTableView.dataSource = self;
self.resultsTableView.delegate = self;

【问题讨论】:

嘿,我无法回答您的问题,但目前我正在尝试获得与您刚才所做的相同的功能。我想知道如何在没有 uitableView 的情况下使用 UiSearchBar 像您一样为地址创建建议列表。能否再提供一些代码示例? 【参考方案1】:

有一种更简单的方法...

对于 ios 8 和 UISearchController,请使用来自 UISearchControllerDelegate 的此委托方法:

func didPresentSearchController(searchController: UISearchController) 
  searchController.searchBar.showsCancelButton = false

别忘了将自己设置为代表:searchController.delegate = self

【讨论】:

这种工作,但取消按钮短暂显示后消失。 @sdsykes 有趣...什么版本的 iOS?它根本不显示给我:( 我发现,按照上面的建议,随后对 showsCancelButton 的调用按预期工作,至少在我的情况下。【参考方案2】:

根据 cmets 更新

UISearchBar 有一个属性(参见Apple docs)确定是否显示取消按钮:

self.searchBar.showsCancelButton = false;

但是,根据 OP cmets,这不起作用,因为 searchController 不断将取消按钮重新打开。为避免这种情况,请创建 UISearchBar 的子类,并覆盖 setShowsCancelButton 方法:

@implementation MySearchBar

-(void)setShowsCancelButton:(BOOL)showsCancelButton 
    // Do nothing...


-(void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated 
    // Do nothing....


@end

为了确保这个子类被 searchController 使用,我们还需要继承 UISearchController,并重写 searchBar 方法以返回我们子类的实例。我们还需要确保新的 searchBar 激活 searchController - 为此我选择使用 UISearchBarDelegate 方法 textDidChange

@interface MySearchController ()  <UISearchBarDelegate> 
UISearchBar *_searchBar;

@end

@implementation MySearchController

-(UISearchBar *)searchBar 
    if (_searchBar == nil) 
        _searchBar = [[MySearchBar alloc] initWithFrame:CGRectZero];
        _searchBar.delegate = self;
    
    return _searchBar;


-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText 
    if ([searchBar.text length] > 0) 
        self.active = true;
     else 
        self.active = false;
    

@end

最后,改变你的代码来实例化这个子类:

self.searchController = [[MySearchController alloc] initWithSearchResultsController:self.resultsViewController];

(您显然需要为这些子类导入相关的头文件)。

【讨论】:

不幸的是不起作用,我相信这与搜索栏本身的取消按钮有关。这个搜索按钮似乎是由 UISearchController 控制的 @simonthumper 我已经用涉及子类化 searchBar 的解决方案更新了我的答案。 谢谢,这是我正在考虑的,但希望有一个更简单的解决方案。我今晚可能会有机会实施,然后我会标记为正确:) updateSearchResultsForSearchController 在我使用此方法时停止工作。有什么建议吗? @vinnybad 抱歉,在我实现 textDidChange 方法来激活搜索控制器之前,我遇到了这个问题。但这为我解决了问题。【参考方案3】:

Swift3 中的简单解决方案 - 我们需要使 CustomSearchBar 没有取消按钮,然后在新的 CustomSearchController 中覆盖相应的属性:

class CustomSearchBar: UISearchBar 

override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) 
    super.setShowsCancelButton(false, animated: false)


class CustomSearchController: UISearchController 
lazy var _searchBar: CustomSearchBar = 
    [unowned self] in
    let customSearchBar = CustomSearchBar(frame: CGRect.zero)
    return customSearchBar
    ()

override var searchBar: UISearchBar 
    get 
        return _searchBar
    

在 MyViewController 中,我使用这个新的自定义子类初始化和配置 searchController:

    var videoSearchController: UISearchController = (
    // Display search results in a separate view controller
    //        let storyBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
    //        let alternateController = storyBoard.instantiateViewController(withIdentifier: "aTV") as! AlternateTableViewController
    //        let controller = UISearchController(searchResultsController: alternateController)
    let controller = CustomSearchController(searchResultsController: nil)
    controller.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
    controller.hidesNavigationBarDuringPresentation = false
    controller.dimsBackgroundDuringPresentation = false
    controller.searchBar.searchBarStyle = .minimal
    controller.searchBar.sizeToFit()
    return controller
)()

而且它工作正常且流畅

【讨论】:

完美运行,也适用于 Swift 4.2 和 iOS 11/12。【参考方案4】:

你可以这样做:

- (void)willPresentSearchController:(UISearchController *)searchController 
dispatch_async(dispatch_get_main_queue(), ^
    searchController.searchBar.showsCancelButton = NO;
); 

【讨论】:

【参考方案5】:

@pbasdf 的答案在大多数情况下都有效,但检查searchText 长度以确定UISearchController 是否处于活动状态可以为用户增加更多工作。极端情况是用户点击 clear 按钮,或删除搜索栏中的唯一字符。这会将active 设置为NO,这将自动在UISearchBar 上调用resignFirstResponder。键盘会消失,如果用户想要更改文本或输入更多文本,则需要再次点击搜索栏。

相反,如果搜索栏不是第一响应者(键盘未激活和显示),我只将 active 设置为 NO,因为这实际上是一个 cancel 命令。 p>

FJSearchBar

标记searchController.searchBar.showsCancelButton = NO 似乎在iOS 8 中不起作用。我还没有测试过 iOS 9

FJSearchBar.h

空的,但为了完整起见放在此处。

@import UIKit;

@interface FJSearchBar : UISearchBar

@end

FJSearchBar.m

#import "FJSearchBar.h"

@implementation FJSearchBar

- (void)setShowsCancelButton:(BOOL)showsCancelButton 
    // do nothing


- (void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated 
    // do nothing


@end

FJSearchController

您要在此处进行真正的更改。我将UISearchBarDelegate 拆分为它自己的类别,因为恕我直言,这些类别使类更干净且更易于维护。如果您想将委托保留在主类接口/实现中,我们非常欢迎您这样做。

FJSearchController.h

@import UIKit;

@interface FJSearchController : UISearchController

@end

@interface FJSearchController (UISearchBarDelegate) <UISearchBarDelegate>

@end

FJSearchController.m

#import "FJSearchController.h"
#import "FJSearchBar.h"

@implementation FJSearchController 
@private
    FJSearchBar *_searchBar;
    BOOL _clearedOutside;


- (UISearchBar *)searchBar 
    if (_searchBar == nil) 
        // if you're not hiding the cancel button, simply uncomment the line below and delete the FJSearchBar alloc/init
        // _searchBar = [[UISearchBar alloc] init];
        _searchBar = [[FJSearchBar alloc] init];
        _searchBar.delegate = self;
    
    return _searchBar;


@end

@implementation FJSearchController (UISearchBarDelegate)

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar 
    // if we cleared from outside then we should not allow any new editing
    BOOL shouldAllowEditing = !_clearedOutside;
    _clearedOutside = NO;
    return shouldAllowEditing;


- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar 
    // hide the keyboard since the user will no longer add any more input
    [searchBar resignFirstResponder];


- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText 
    if (![searchBar isFirstResponder]) 
        // the user cleared the search while not in typing mode, so we should deactivate searching
        self.active = NO;
        _clearedOutside = YES;
        return;
    
    // update the search results
    [self.searchResultsUpdater updateSearchResultsForSearchController:self];


@end

一些需要注意的部分:

    我已将搜索栏和BOOL 作为私有变量而不是属性,因为 它们比私有属性更轻量级。 它们不需要被外界看到或修改。 我们检查searchBar 是否是第一响应者。如果不是,那么我们实际上停用了搜索控制器,因为文本为空并且我们不再搜索。如果你真的想确定,你也可以确定searchText.length == 0searchBar:textDidChange:searchBarShouldBeginEditing: 之前调用,这就是我们按此顺序处理它的原因。 我会在每次文本更改时更新搜索结果,但如果您只想在用户按下 搜索 按钮后执行搜索,则可能需要将 [self.searchResultsUpdater updateSearchResultsForSearchController:self]; 移动到 searchBarSearchButtonClicked:

【讨论】:

【参考方案6】:

通过在几个UISearchBarDelegate 方法中调用setShowsCancelButton,我能够使UISearchBar 的行为符合预期,而无需子类化:

我称之为textDidChangesearchBarCancelButtonClicked。这是我的实现的样子:

extension MyViewController: UISearchBarDelegate 

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) 
        if searchText.characters.isEmpty == false 
            searchBar.setShowsCancelButton(true, animated: true)

            // whatever extra stuff you need to do
         else 
            searchBar.setShowsCancelButton(false, animated: true)

            // whatever extra stuff you need to do
        
        // whatever extra stuff you need to do
    

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) 
        searchBar.setShowsCancelButton(false, animated: false)
        searchBar.text = nil
        searchBar.resignFirstResponder()
        tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
        // whatever extra stuff you need to do
    

【讨论】:

以上是关于UISearchController 禁用取消 UIBarButtonItem的主要内容,如果未能解决你的问题,请参考以下文章

iOS UISearchController:等到 UISearchController 在取消搜索后被解除

UISearchController 中的取消按钮

按下取消按钮时如何防止 UISearchController 关闭?

UISearchController 更改“取消”按钮标题

UISearchController 在点击取消按钮时关闭 VC

在 UISearchController 中按取消后导航栏被阻止