UISearchController 禁用取消 UIBarButtonItem

UISearchController disable cancel UIBarButtonItem

问题

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

这个取消按钮有时会消失,当我在玩的时候,但我不能让它不出现,现在我已经搜索 table 显示我想要它的方式:

我敢肯定一定有什么地方我做错了,但我不知道是什么...

我的代码

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;

根据评论更新

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

self.searchBar.showsCancelButton = false;

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

@implementation MySearchBar

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

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

@end

为了确保这个子类被 searchController 使用,我们还需要继承 UISearchController,并将 searchBar 方法重写为 return 我们子类的一个实例。我们还需要确保新的 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];

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

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

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

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

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

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

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

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 拆分到它自己的类别中,因为恕我直言,这些类别使 classes 更干净,更易于维护。如果您想将代表保留在主要 class interface/implementation 内,我们非常欢迎您这样做。

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

需要注意的部分:

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

你可以这样做:

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

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

并且运行正常流畅

通过在几个 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
    }
}