UISearchController:即使搜索栏为空也显示结果
UISearchController: show results even when search bar is empty
据我了解,UISearchController
的默认行为是:
- 点击搜索栏时,背景变暗并显示 'cancel' 按钮。
SearchResultsController
直到此时才显示。
SearchResultsController
仅在搜索栏不为空时显示。
我想显示 SearchResultsController
即使搜索栏为空但已选中(即上面的情况 1)。
简而言之,我想显示搜索结果而不是调暗背景。
有办法吗?
更多说明:
我没有使用 UISearchController
来过滤在显示它的视图上显示的结果,而是一些其他不相关的结果。
就像 facebook 在其 'News Feed' 上所做的一样。点击搜索栏最初会显示搜索建议,然后,当我们开始编辑时,它会显示可能与新闻提要无关的搜索结果。
我想你错了。
SearchResultsController 仅在有结果时出现。这与您的解释略有不同。
结果是根据搜索栏中的文本手动加载的。因此,如果搜索栏为空并且 return 您自己的一组结果,您可以拦截它。
如果您不想使结果变暗,请将 dimsBackgroundDuringPresentation
属性 设置为 false
。
这将确保底层内容在搜索过程中不会变暗。
即使 searchText 为空,您也必须确保 return 结果,否则将显示空的表格视图。
如果您的搜索栏处于活动状态但没有文本,则会显示基础 tableView 结果。这是内置行为,也是 searchResultsController 在该状态下隐藏的原因。
要在搜索处于活动状态但不过滤时更改行为,您将必须在通常情况下仍处于隐藏状态时显示 searchResultsController。
通过 <UISearchResultsUpdating>
和 updateSearchResultsForSearchController:
可能是实现此目的的好方法。如果你能通过协议解决它,那是首选的方式。
如果这没有帮助,您只能修改内置行为。我不会推荐或依赖它,它会很脆弱,但如果您选择该选项,这里有一个答案:
确保你的tableViewController符合<UISearchControllerDelegate>
,并添加
self.searchController.delegate = self;
实施willPresentSearchController:
- (void)willPresentSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_main_queue(), ^{
searchController.searchResultsController.view.hidden = NO;
});
}
这使得 searchResultsController
在其 UISearchController
设置为隐藏后可见。
实施didPresentSearchController:
- (void)didPresentSearchController:(UISearchController *)searchController
{
searchController.searchResultsController.view.hidden = NO;
}
有关解决内置行为的更好方法,请参阅 。
我试过 PetahChristian 解决方案,当我们第一次聚焦搜索栏时预加载结果确实显示,但是当我们输入内容然后清除它时,预加载结果不会重新出现。
我想到了另一个解决方案。我们只需要在 SearchResultsController 中添加一个委托,并在 searchController.searchBar.text 为空时调用它。像这样:
搜索结果控制器:
protocol SearchResultsViewControllerDelegate {
func reassureShowingList() -> Void
}
class FullSearchResultsViewController: UIViewController, UISearchResultsUpdating{
var delegate: SearchResultsViewControllerDelegate?
...
func updateSearchResultsForSearchController(searchController: UISearchController) {
let query = searchController.searchBar.text?.trim()
if query == nil || query!.isEmpty {
...
self.delegate?.reassureShowingList()
...
}
...
}
并且在包含 SearchController 的控制器中,我们添加委托:
self.searchResultsController.delegate = self
func reassureShowingList() {
searchController.searchResultsController!.view.hidden = false
}
对于像这样棘手的事情,我推荐大锤方法!那就是检测何时有东西试图隐藏它,当它隐藏时,把它改回来。这可以通过 KVO(键值观察)来完成。无论如何这都会起作用,而不必处理搜索栏的所有复杂性。抱歉,代码很复杂,但 KVO 是一种较旧的样式 API,但我的代码遵循推荐做法。在您的 SearchResultsViewController 中输入:
static int kHidden;
@implementation SearchResultsViewController
-(void)viewDidLoad{
[super viewDidLoad];
[self.view addObserver:self
forKeyPath:@"hidden"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:&kHidden];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
// if it was our observation
if(context == &kHidden){
// if the view is hidden then make it visible.
if([[change objectForKey:NSKeyValueChangeNewKey] boolValue]){
self.view.hidden = NO;
}
}
else{
// if necessary, pass the method up the subclass hierarchy.
if([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
}
- (void)dealloc
{
[self.view removeObserver:self forKeyPath:@"hidden"];
}
// Here have the rest of your code for the search results table.
@end
这适用于所有情况,包括清除文本的情况。
最后,为了防止 table 在搜索激活时先变灰后变白,使用这个:
self.searchController.dimsBackgroundDuringPresentation = NO;
您可以简单地实现 UISearchResultsUpdating
协议并将结果控制器视图设置为始终显示在 updateSearchResultsForSearchController
:
func updateSearchResultsForSearchController(searchController: UISearchController) {
// Always show the search result controller
searchController.searchResultsController?.view.hidden = false
// Update your search results data and reload data
..
}
这是有效的,因为即使在搜索栏被激活时也会调用该方法,没有任何文本。
我花了很多时间解决这个问题,最终我采用的解决方案就像@malhals 的解决方案,但是通过使用 facebook 的 KVOController: https://github.com/facebook/KVOController 显着减少了代码量。这里的另一个优点是,如果你的 searchResultsController 是一个 UINavigationController
那么你不需要子类化它只是为了添加@malhal 的代码。
// always show searchResultsController, even if text is empty
[self.KVOController observe:self.searchController.searchResultsController.view keyPath:@"hidden" options:NSKeyValueObservingOptionNew block:^(id observer, UIView* view, NSDictionary *change) {
if ([change[NSKeyValueChangeNewKey] boolValue] == YES) {
view.hidden = NO;
}
}];
self.searchController.dimsBackgroundDuringPresentation = NO;
最简单的方法是使用带有此扩展的 ReactiveCocoa https://github.com/ColinEberhardt/ReactiveTwitterSearch/blob/master/ReactiveTwitterSearch/Util/UIKitExtensions.swift
presentViewController(sc, animated: true, completion: {
sc.searchResultsController?.view.rac_hidden.modify({ value -> Bool in
return false
})
} )
其中 sc 是您的 UISearchController
@malhal 方法的 Swift 2.3 版本:
class SearchResultsViewController : UIViewController {
var context = 0
override func viewDidLoad() {
super.viewDidLoad()
// Add observer
view.addObserver(self, forKeyPath: "hidden", options: [ .New, .Old ], context: &context)
}
deinit {
view.removeObserver(self, forKeyPath: "hidden")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &self.context {
if change?[NSKeyValueChangeNewKey] as? Bool == true {
view.hidden = false
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
Swift 3 版本:
如果您的 searchResultController
不是 nil 并且您正在使用单独的 table 视图控制器来显示搜索结果,那么您可以使 table 视图控制器符合 UISearchResultUpdating
并且在 updateSearchResults
函数中,您可以简单地取消隐藏视图。
func updateSearchResults(for searchController: UISearchController) {
view.hidden = false
}
Swift 4 版本:
func updateSearchResults(for searchController: UISearchController) {
view.isHidden = false
}
更新 iOS 13
从 iOS13 开始,我们得到了系统 API 对此行为的支持。您可以设置 属性 showsSearchResultsController = true
iOS12岁及以下
我最近在研究 UISearchController
。当搜索栏为空时,我想在 searchResultsController
中显示搜索历史记录。所以 searchResultsController
需要在出现 UISearchController
时出现。
在这里,我使用另一种解决方案,通过 覆盖自定义视图 中的 hidden
属性 使 searchResultsController
始终可见。
比如我的searchResultsController
是UITableViewController
。我创建一个 VisibleTableView 作为 UITableView
的子 class,然后更改 searchResultsController
的 UITableView
自定义 class到 xib 或故事板中的 VisibleTableView。这样我的searchResultsController
就永远不会被UISearchController
隐藏了。
这里的好东西:
比KVO更容易实现
没有延迟显示 searchResultsController
。在 "updateSearchResults" 委托方法中翻转隐藏标志有效,但显示 searchResultsController
.
有延迟
它不重置隐藏标志,所以隐藏和可见之间没有UI gap/jumping。
Swift 3 示例代码:
class VisibleTableView: UITableView {
override var isHidden: Bool {
get {
return false
}
set {
// ignoring any settings
}
}
}
隐藏的是搜索结果控制器的视图。因此,只要它可能被隐藏,就可以随时取消隐藏它。只需在搜索结果控制器中执行以下操作:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.isHidden = false
}
func updateSearchResults(for searchController: UISearchController) {
self.view.isHidden = false
// ... your other code goes here ...
}
现在结果视图(即 table 视图)始终可见,即使搜索栏文本为空。
顺便说一下,iOS 邮件应用程序的行为是这样的,我认为这就是它的实现方式(除非 Apple 可以访问一些秘密的私有 UISearchController 设置)。
[在 iOS 10 和 iOS 11 中测试;我没有在任何早期系统上进行测试。]
我真的很喜欢 Simon Wang 的回答并使用它,这就是我所做的并且它完美地工作:
我在自定义 class:
中子class UISearchController
class CustomClass: UISearchController {
override var searchResultsController: UIViewController? {
get {
let viewController = super.searchResultsController
viewController?.view.isHidden = false
return viewController
}
set {
// nothing
}
}
}
还要确保您的代码中没有任何地方:
self.resultsSearchController.isActive = true
resultsSearchController 是我的 UISearchController
Swift 4 版本 :
class SearchController: UISearchController {
private var viewIsHiddenObserver: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
viewIsHiddenObserver = self.searchResultsController?.view.observe(\.hidden, changeHandler: { [weak self] (view, _) in
guard let searchController = self else {return}
if view.isHidden && searchController.searchBar.isFirstResponder {
view.isHidden = false
}
})
}
}
请注意[weak self]
。否则你会引入一个保留周期。
就是我用的这个案例
func updateSearchResults(for searchController: UISearchController) {
if let inputText = searchController.searchBar.text, !inputText.isEmpty {
self.view.isHidden = false
}
}
其中 self.view 是 UISearchController 初始化期间“searchResultsController”的视图。
var searchController = UISearchController(searchResultsController: searchResultsController)
据我了解,UISearchController
的默认行为是:
- 点击搜索栏时,背景变暗并显示 'cancel' 按钮。
SearchResultsController
直到此时才显示。 SearchResultsController
仅在搜索栏不为空时显示。
我想显示 SearchResultsController
即使搜索栏为空但已选中(即上面的情况 1)。
简而言之,我想显示搜索结果而不是调暗背景。
有办法吗?
更多说明:
我没有使用 UISearchController
来过滤在显示它的视图上显示的结果,而是一些其他不相关的结果。
就像 facebook 在其 'News Feed' 上所做的一样。点击搜索栏最初会显示搜索建议,然后,当我们开始编辑时,它会显示可能与新闻提要无关的搜索结果。
我想你错了。
SearchResultsController 仅在有结果时出现。这与您的解释略有不同。
结果是根据搜索栏中的文本手动加载的。因此,如果搜索栏为空并且 return 您自己的一组结果,您可以拦截它。
如果您不想使结果变暗,请将 dimsBackgroundDuringPresentation
属性 设置为 false
。
这将确保底层内容在搜索过程中不会变暗。
即使 searchText 为空,您也必须确保 return 结果,否则将显示空的表格视图。
如果您的搜索栏处于活动状态但没有文本,则会显示基础 tableView 结果。这是内置行为,也是 searchResultsController 在该状态下隐藏的原因。
要在搜索处于活动状态但不过滤时更改行为,您将必须在通常情况下仍处于隐藏状态时显示 searchResultsController。
通过 <UISearchResultsUpdating>
和 updateSearchResultsForSearchController:
可能是实现此目的的好方法。如果你能通过协议解决它,那是首选的方式。
如果这没有帮助,您只能修改内置行为。我不会推荐或依赖它,它会很脆弱,但如果您选择该选项,这里有一个答案:
确保你的tableViewController符合
<UISearchControllerDelegate>
,并添加self.searchController.delegate = self;
实施
willPresentSearchController:
- (void)willPresentSearchController:(UISearchController *)searchController { dispatch_async(dispatch_get_main_queue(), ^{ searchController.searchResultsController.view.hidden = NO; }); }
这使得
searchResultsController
在其UISearchController
设置为隐藏后可见。实施
didPresentSearchController:
- (void)didPresentSearchController:(UISearchController *)searchController { searchController.searchResultsController.view.hidden = NO; }
有关解决内置行为的更好方法,请参阅
我试过 PetahChristian 解决方案,当我们第一次聚焦搜索栏时预加载结果确实显示,但是当我们输入内容然后清除它时,预加载结果不会重新出现。
我想到了另一个解决方案。我们只需要在 SearchResultsController 中添加一个委托,并在 searchController.searchBar.text 为空时调用它。像这样:
搜索结果控制器:
protocol SearchResultsViewControllerDelegate {
func reassureShowingList() -> Void
}
class FullSearchResultsViewController: UIViewController, UISearchResultsUpdating{
var delegate: SearchResultsViewControllerDelegate?
...
func updateSearchResultsForSearchController(searchController: UISearchController) {
let query = searchController.searchBar.text?.trim()
if query == nil || query!.isEmpty {
...
self.delegate?.reassureShowingList()
...
}
...
}
并且在包含 SearchController 的控制器中,我们添加委托:
self.searchResultsController.delegate = self
func reassureShowingList() {
searchController.searchResultsController!.view.hidden = false
}
对于像这样棘手的事情,我推荐大锤方法!那就是检测何时有东西试图隐藏它,当它隐藏时,把它改回来。这可以通过 KVO(键值观察)来完成。无论如何这都会起作用,而不必处理搜索栏的所有复杂性。抱歉,代码很复杂,但 KVO 是一种较旧的样式 API,但我的代码遵循推荐做法。在您的 SearchResultsViewController 中输入:
static int kHidden;
@implementation SearchResultsViewController
-(void)viewDidLoad{
[super viewDidLoad];
[self.view addObserver:self
forKeyPath:@"hidden"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:&kHidden];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
// if it was our observation
if(context == &kHidden){
// if the view is hidden then make it visible.
if([[change objectForKey:NSKeyValueChangeNewKey] boolValue]){
self.view.hidden = NO;
}
}
else{
// if necessary, pass the method up the subclass hierarchy.
if([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
}
- (void)dealloc
{
[self.view removeObserver:self forKeyPath:@"hidden"];
}
// Here have the rest of your code for the search results table.
@end
这适用于所有情况,包括清除文本的情况。
最后,为了防止 table 在搜索激活时先变灰后变白,使用这个:
self.searchController.dimsBackgroundDuringPresentation = NO;
您可以简单地实现 UISearchResultsUpdating
协议并将结果控制器视图设置为始终显示在 updateSearchResultsForSearchController
:
func updateSearchResultsForSearchController(searchController: UISearchController) {
// Always show the search result controller
searchController.searchResultsController?.view.hidden = false
// Update your search results data and reload data
..
}
这是有效的,因为即使在搜索栏被激活时也会调用该方法,没有任何文本。
我花了很多时间解决这个问题,最终我采用的解决方案就像@malhals 的解决方案,但是通过使用 facebook 的 KVOController: https://github.com/facebook/KVOController 显着减少了代码量。这里的另一个优点是,如果你的 searchResultsController 是一个 UINavigationController
那么你不需要子类化它只是为了添加@malhal 的代码。
// always show searchResultsController, even if text is empty
[self.KVOController observe:self.searchController.searchResultsController.view keyPath:@"hidden" options:NSKeyValueObservingOptionNew block:^(id observer, UIView* view, NSDictionary *change) {
if ([change[NSKeyValueChangeNewKey] boolValue] == YES) {
view.hidden = NO;
}
}];
self.searchController.dimsBackgroundDuringPresentation = NO;
最简单的方法是使用带有此扩展的 ReactiveCocoa https://github.com/ColinEberhardt/ReactiveTwitterSearch/blob/master/ReactiveTwitterSearch/Util/UIKitExtensions.swift
presentViewController(sc, animated: true, completion: {
sc.searchResultsController?.view.rac_hidden.modify({ value -> Bool in
return false
})
} )
其中 sc 是您的 UISearchController
@malhal 方法的 Swift 2.3 版本:
class SearchResultsViewController : UIViewController {
var context = 0
override func viewDidLoad() {
super.viewDidLoad()
// Add observer
view.addObserver(self, forKeyPath: "hidden", options: [ .New, .Old ], context: &context)
}
deinit {
view.removeObserver(self, forKeyPath: "hidden")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &self.context {
if change?[NSKeyValueChangeNewKey] as? Bool == true {
view.hidden = false
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
Swift 3 版本:
如果您的 searchResultController
不是 nil 并且您正在使用单独的 table 视图控制器来显示搜索结果,那么您可以使 table 视图控制器符合 UISearchResultUpdating
并且在 updateSearchResults
函数中,您可以简单地取消隐藏视图。
func updateSearchResults(for searchController: UISearchController) {
view.hidden = false
}
Swift 4 版本:
func updateSearchResults(for searchController: UISearchController) {
view.isHidden = false
}
更新 iOS 13
从 iOS13 开始,我们得到了系统 API 对此行为的支持。您可以设置 属性 showsSearchResultsController = true
iOS12岁及以下
我最近在研究 UISearchController
。当搜索栏为空时,我想在 searchResultsController
中显示搜索历史记录。所以 searchResultsController
需要在出现 UISearchController
时出现。
在这里,我使用另一种解决方案,通过 覆盖自定义视图 中的 hidden
属性 使 searchResultsController
始终可见。
比如我的searchResultsController
是UITableViewController
。我创建一个 VisibleTableView 作为 UITableView
的子 class,然后更改 searchResultsController
的 UITableView
自定义 class到 xib 或故事板中的 VisibleTableView。这样我的searchResultsController
就永远不会被UISearchController
隐藏了。
这里的好东西:
比KVO更容易实现
没有延迟显示
searchResultsController
。在 "updateSearchResults" 委托方法中翻转隐藏标志有效,但显示searchResultsController
. 有延迟
它不重置隐藏标志,所以隐藏和可见之间没有UI gap/jumping。
Swift 3 示例代码:
class VisibleTableView: UITableView {
override var isHidden: Bool {
get {
return false
}
set {
// ignoring any settings
}
}
}
隐藏的是搜索结果控制器的视图。因此,只要它可能被隐藏,就可以随时取消隐藏它。只需在搜索结果控制器中执行以下操作:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.isHidden = false
}
func updateSearchResults(for searchController: UISearchController) {
self.view.isHidden = false
// ... your other code goes here ...
}
现在结果视图(即 table 视图)始终可见,即使搜索栏文本为空。
顺便说一下,iOS 邮件应用程序的行为是这样的,我认为这就是它的实现方式(除非 Apple 可以访问一些秘密的私有 UISearchController 设置)。
[在 iOS 10 和 iOS 11 中测试;我没有在任何早期系统上进行测试。]
我真的很喜欢 Simon Wang 的回答并使用它,这就是我所做的并且它完美地工作:
我在自定义 class:
中子class UISearchControllerclass CustomClass: UISearchController {
override var searchResultsController: UIViewController? {
get {
let viewController = super.searchResultsController
viewController?.view.isHidden = false
return viewController
}
set {
// nothing
}
}
}
还要确保您的代码中没有任何地方:
self.resultsSearchController.isActive = true
resultsSearchController 是我的 UISearchController
Swift 4 版本
class SearchController: UISearchController {
private var viewIsHiddenObserver: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
viewIsHiddenObserver = self.searchResultsController?.view.observe(\.hidden, changeHandler: { [weak self] (view, _) in
guard let searchController = self else {return}
if view.isHidden && searchController.searchBar.isFirstResponder {
view.isHidden = false
}
})
}
}
请注意[weak self]
。否则你会引入一个保留周期。
就是我用的这个案例
func updateSearchResults(for searchController: UISearchController) {
if let inputText = searchController.searchBar.text, !inputText.isEmpty {
self.view.isHidden = false
}
}
其中 self.view 是 UISearchController 初始化期间“searchResultsController”的视图。
var searchController = UISearchController(searchResultsController: searchResultsController)