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
需要注意的部分:
- 我将搜索栏和
BOOL
作为私有变量而不是属性,因为
- 它们比私有属性更轻量级。
- 它们不需要被外界看到或修改。
- 我们检查
searchBar
是否是第一响应者。如果不是,那么我们实际上停用了搜索控制器,因为文本是空的,我们不再搜索。如果你真的想要确定,你也可以确保searchText.length == 0
.
searchBar:textDidChange:
在 searchBarShouldBeginEditing:
之前被调用,这就是我们按此顺序处理它的原因。
- 每次文本更改时我都会更新搜索结果,但如果您只想在用户按下 [=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
表现得像预期的那样:
我在textDidChange
和searchBarCancelButtonClicked
中调用它。这是我的实现方式:
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 在地图视图中搜索目的地。我希望 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
需要注意的部分:
- 我将搜索栏和
BOOL
作为私有变量而不是属性,因为- 它们比私有属性更轻量级。
- 它们不需要被外界看到或修改。
- 我们检查
searchBar
是否是第一响应者。如果不是,那么我们实际上停用了搜索控制器,因为文本是空的,我们不再搜索。如果你真的想要确定,你也可以确保searchText.length == 0
. searchBar:textDidChange:
在searchBarShouldBeginEditing:
之前被调用,这就是我们按此顺序处理它的原因。- 每次文本更改时我都会更新搜索结果,但如果您只想在用户按下 [=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
表现得像预期的那样:
我在textDidChange
和searchBarCancelButtonClicked
中调用它。这是我的实现方式:
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
}
}