UISearchbar清除按钮会强制显示键盘

45

我有一个 UISearchBar,它作为表视图的实时筛选器。当使用 endEditing: 方法关闭键盘时,查询文本和灰色圆形“清除”按钮仍然存在。从这里开始,如果我点击灰色的“清除”按钮,文本被清除后键盘会重新出现。

我该如何防止这种情况发生?如果键盘当前未打开,则希望该按钮在不重新打开键盘的情况下清除文本。

当我点击清除按钮时,有一个协议方法会被调用。但是向 UISearchBar 发送 resignFirstResponder 消息对键盘没有任何影响。

12个回答

124
这是一个旧问题,我刚遇到同样的问题,并设法通过以下方式解决:
当UISearchBarDelegate的searchBar:textDidChange:方法由于用户点击“清除”按钮而被调用时,searchBar尚未成为第一响应者,因此我们可以利用这一点来检测用户实际上是打算清除搜索还是将焦点放在searchBar上并/或执行其他操作。
为了跟踪这一点,我们需要在viewController中声明一个BOOL ivar(让我们称其为shouldBeginEditing),它也是searchBar代理,并将其初始值设置为YES(假设我们的viewController类名为SearchViewController):
@interface SearchViewController : UIViewController <UISearchBarDelegate> {
    // all of our ivar declarations go here...
    BOOL shouldBeginEditing;
    ....
}

...
@end



@implementation SearchViewController
...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        ...
        shouldBeginEditing = YES;
    }
}
...
@end

后来,在UISearchBarDelegate中,我们实现了searchBar:textDidChange:searchBarShouldBeginEditing:方法:

- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)searchText {
    NSLog(@"searchBar:textDidChange: isFirstResponder: %i", [self.searchBar isFirstResponder]);
    if(![searchBar isFirstResponder]) {
        // user tapped the 'clear' button
        shouldBeginEditing = NO;
        // do whatever I want to happen when the user clears the search...
    }
}


- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)bar {
    // reset the shouldBeginEditing BOOL ivar to YES, but first take its value and use it to return it from the method call
    BOOL boolToReturn = shouldBeginEditing;
    shouldBeginEditing = YES;
    return boolToReturn;
}

基本上就是这样。

最好的。


1
这似乎没有考虑到用户已经在输入文本(使搜索栏成为第一响应者)的情况,而在此过程中按下清除按钮。您也有解决方案吗? - thgc
在这种情况下,键盘已经处于“上升”状态。此解决方案适用于用户点击“清除”按钮时不想弹出键盘的显式情况。 - boliva
1
这个解决方案对我很有效。谢谢。我正要开始构建自己的搜索栏,但是在尝试实现自定义UI和行为时,UISearchBar存在太多陷阱和困难。 - n8tr
1
运作得像魅力!感谢 @bolivia - iosCurator

33

我发现当通过点击“清除按钮”触发textDidChange时,resignFirstResponder不起作用。然而,使用performSelection: withObject: afterDelay:似乎是一个有效的解决方法:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if ([searchText length] == 0) {
        [self performSelector:@selector(hideKeyboardWithSearchBar:) withObject:searchBar afterDelay:0];
    }
}

- (void)hideKeyboardWithSearchBar:(UISearchBar *)searchBar
{   
    [searchBar resignFirstResponder];   
}

我花了好几个小时来解决一个类似的问题。这个解决方案对我很有效。非常感谢。 - Vignesh PT

10

我找到了一种相当安全的方法来判断是否按下了清除按钮,并忽略用户只是删除了UISearchBar的最后一个字符的情况。这就是方法:

- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    _isRemovingTextWithBackspace = ([searchBar.text stringByReplacingCharactersInRange:range withString:text].length == 0);

    return YES;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if (searchText.length == 0 && !_isRemovingTextWithBackspace)
    {
        NSLog(@"Has clicked on clear !");
    }
}

非常简单明了,不是吗 :) ? 唯一需要注意的是,如果用户在编辑UISearchBar的UITextField时点击了清除按钮,您会收到两个ping,而如果用户在它没有被编辑时点击了它,您只会收到一个ping。


编辑: 我无法测试,但这是根据Rotem提供的Swift版本:

var isRemovingTextWithBackspace = false

func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool
{
    self.isRemovingTextWithBackspace = (NSString(string: searchBar.text!).stringByReplacingCharactersInRange(range, withString: text).characters.count == 0)
    return true
}

func searchBar(searchBar: UISearchBar, textDidChange searchText: String)
{
    if searchText.characters.count == 0 && !isRemovingTextWithBackspace
    { 
        NSLog("Has clicked on clear !")
    }
}

@Rotem的更新(Swift2):

var isRemovingTextWithBackspace = false

func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    self.isRemovingTextWithBackspace = (NSString(string: searchBar.text!).stringByReplacingCharactersInRange(range, withString: text).characters.count == 0)
    return true
}

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    if searchText.characters.count == 0 && !isRemovingTextWithBackspace {
        NSLog("Has clicked on clear!")
    }
}

Swift 2代码:`var isRemovingTextWithBackspace = falsefunc searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool { self.isRemovingTextWithBackspace = (NSString(string: searchBar.text!).stringByReplacingCharactersInRange(range, withString: text).characters.count == 0) return true } func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { if searchText.characters.count == 0 && !isRemovingTextWithBackspace { NSLog("Has clicked on clear !") } }` - Rotem

8

我使用了@boliva的答案和@radiospiel在另一个SO问题中的答案的组合:

@interface SearchViewController : UIViewController <UISearchBarDelegate> {
    // all of our ivar declarations go here...
    BOOL shouldBeginEditing;
    ....
}

...
@end

@implementation SearchViewController
...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        ...
        shouldBeginEditing = YES;
    }
}
...

- (void) searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
    // TODO - dynamically update the search results here, if we choose to do that.

    if (![searchBar isFirstResponder]) {
        // The user clicked the [X] button while the keyboard was hidden
        shouldBeginEditing = NO;
    }
    else if ([searchText length] == 0) {
        // The user clicked the [X] button or otherwise cleared the text.
        [theSearchBar performSelector: @selector(resignFirstResponder)
                        withObject: nil
                        afterDelay: 0.1];
    }
}

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)bar {
    // reset the shouldBeginEditing BOOL ivar to YES, but first take its value and use it to return it from the method call
    BOOL boolToReturn = shouldBeginEditing;
    shouldBeginEditing = YES;
    return boolToReturn;
}
@end

3
在UISearchBar中按下“清除”按钮会自动打开键盘,即使您在textDidChange的UISearchBarDelegate方法内调用searchBar.resignFirstResponder()
要在按下“x”清除UISearchBar时实际隐藏键盘,请使用以下代码。这样,即使在输入UISearchBar时调用textDidChange,您也只有在删除其中所有文本(无论是使用删除按钮还是单击“x”)时才隐藏键盘:
extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar.text!.count == 0 {
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
        } else {
            // Code to make a request here.
        }
    }
}

3

从我的经验来看,最好的解决方案就是在系统清除按钮上放置一个没有文本和透明背景的 UIButton,然后连接一个 IBAction

使用自动布局就更加容易了。

- (IBAction)searchCancelButtonPressed:(id)sender {

    [self.searchBar resignFirstResponder];
    self.searchBar.text = @"";

    // some of my stuff
    self.model.fastSearchText = nil;
    [self.model fetchData];
    [self reloadTableViewAnimated:NO];

}

这种黑客行为可能会带来问题,例如如果设备更大等等。这不是一个可持续的解决方案。 - Ali Mir

2
@boliva的答案的Swift版本。
class MySearchContentController: UISearchBarDelegate {

    private var searchBarShouldBeginEditing = true

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        searchBarShouldBeginEditing = searchBar.isFirstResponder
    }

    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        defer {
            searchBarShouldBeginEditing = true
        }
        return searchBarShouldBeginEditing
    }
}

这绝对是最优雅的工作解决方案!别忘了 searchBar.delegate = self :) - Volodymyr Davydenko

0

如果你是在使用 iOS 8 或更新版本中的 UISearchController,你只需要简单地继承 UISearchController。为了完备起见,你还可以隐藏取消按钮(我也这样做了),因为从 UISearchBar 中清除文本等同于取消搜索。如果你想要使用它,下面附上代码。

这样做的好处在于你可以将其用于任何类和任何视图,而不需要 UIViewController 的子类。最后,我会在此解决方案底部包含如何初始化 UISearchController 的方式。

FJSearchBar

只有在你想要像我一样隐藏取消按钮时,这个类才需要被覆盖。在 iOS 8 中标记 searchController.searchBar.showsCancelButton = NO 似乎不起作用。我没有测试过 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

需要注意的一些部分:

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

0
点击清除按钮会导致searchText变为空。另一种实现方式是在- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)searchText方法中检查空文本:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if([searchText length] == 0)
    {
        [self dismissSearch];
    }
    else
    {
        self.searchResultsTable.hidden = YES;
        [self handleSearchForString:searchText];
    }
}

- (void)dismissSearch
{
    [self.searchBar performSelector: @selector(resignFirstResponder)
                  withObject: nil
                  afterDelay: 0.1];

    self.searchResultsTable.hidden = YES;
}

0
在你的endEditing方法中,为什么不清除UISearchBar呢?既然那里必须是你首先放弃响应者的地方,这样做很有道理。

我不明白你想说什么。如果我像这样放弃搜索栏:- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { [searchBar resignFirstResponder]; }那行不通。 - Jens Kohl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接