UISearchbar clearButton 强制键盘出现

Posted

技术标签:

【中文标题】UISearchbar clearButton 强制键盘出现【英文标题】:UISearchbar clearButton forces the keyboard to appear 【发布时间】:2010-11-08 16:47:42 【问题描述】:

我有一个 UISearchBar 作为表格视图的实时过滤器。当通过 endEditing: 关闭键盘时,查询文本和灰色圆形“清除”按钮仍然存在。从这里,如果我点击灰色的“清除”按钮,键盘会在文本被清除时重新出现。

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

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

【问题讨论】:

【参考方案1】:

这是一个老问题,我刚刚遇到同样的问题并设法通过以下方式解决它:

当 UISearchBarDelegate 的 searchBar:textDidChange: 方法因为用户点击“清除”按钮而被调用时,searchBar 还没有成为第一响应者,所以我们可以利用它来检测用户何时实际上是为了清除搜索,而不是将焦点放在 searchBar 和/或做其他事情。

为了跟踪它,我们需要在 viewController 中声明一个 BOOL ivar,它也是 searchBar 委托(我们称之为 shouldBeginEditing)并将其设置为初始值 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;

基本上就是这样。

最好的

【讨论】:

这似乎没有考虑到用户已经在输入文本(使搜索栏成为第一响应者)的情况,同时按下清除按钮。你也有解决方案吗? @thgc 在这种情况下,键盘已经“启动”了。此解决方案适用于当用户点击“清除”按钮并且我们不希望键盘出现时的显式情况。 这个解决方案对我有用。谢谢你。我正要经历构建自己的搜索栏的痛苦。 UISearchBar 在尝试实现自定义 UI 和行为时有太多的陷阱和困难。 像魅力一样工作!谢谢@玻利维亚【参考方案2】:

我发现当从触摸到“清除按钮”调用 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];   

【讨论】:

花了几个小时试图解决一个类似的问题。这个解决方案对我有用。非常感谢。【参考方案3】:

我找到了一种非常安全的方法来了解是否已按下清除按钮,并忽略用户刚刚删除 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,而如果用户在未编辑时单击它,则只会收到一个。


编辑: 我无法测试它,但这里是 Swift 版本,根据 Rotem :

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 = false func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -&gt; 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 &amp;&amp; !isRemovingTextWithBackspace NSLog("Has clicked on clear !") 【参考方案4】:

我结合了@boliva 的答案和@radiospiel 的answer 来回答一个不同的 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

【讨论】:

谢谢伙计。你让我今天一整天都感觉很好。非常感谢。【参考方案5】:

根据我的经验,最好的解决方案是在系统清除按钮上方放置一个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];


【讨论】:

这个黑客可能会出现问题,例如设备是否更大等等。这不是一个可持续的解决方案【参考方案6】:

即使您在textDidChange UISearchBarDelegate 方法中调用searchBar.resignFirstResponder(),按下 UISearchBar 中的“清除”按钮也会自动打开键盘。

要在按下“x”清除 UISearchBar 时真正隐藏键盘,请使用以下代码。这样,即使 textDidChange 在 UISearchBar 中键入内容时被调用,也只有在删除其中的所有文本、使用删除按钮或单击“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.
        
    

【讨论】:

【参考方案7】:

@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 :)【参考方案8】:

在你的 endEditing 方法中,你为什么不清除 UISearchBar 呢?由于这也必须是您辞去第一响应者的地方,所以这是有道理的。

【讨论】:

我不明白你想说什么。如果我像这样辞职搜索栏: - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar [searchBar resignFirstResponder]; 这不起作用。【参考方案9】:

有一个搜索栏委托调用要求您接受从旧值到新值的更改 - 您可以检测到新值是 nil,旧值不是 nil,并且指示自从键盘上次启动以来,用户没有输入任何内容 - 然后在这种情况下辞去搜索栏的第一响应者。不确定键盘是否会暂时显示。

我也有类似的情况,可以自己试试。

【讨论】:

【参考方案10】:

触摸清除按钮会导致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;

【讨论】:

【参考方案11】:

对于那些在 iOS 8 及更高版本中使用UISearchController 的用户,您只需将UISearchController 子类化即可。为了完整起见,您可能还想像我一样隐藏 cancel 按钮,因为从 UISearchBar 清除文本实际上是取消搜索。如果您想使用它,我在下面添加了该代码。

这样做的好处是您可以将它用于任何类和任何视图,而不需要UIViewController 的子类。我什至会在这个解决方案的底部包括我如何初始化我的UISearchController

FJSearchBar

只有当你想像我一样隐藏取消按钮时,才需要重写这个类。标记searchController.searchBar.showsCancelButton = NOiOS 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:

【讨论】:

【参考方案12】:

我已经遇到过好几次了。我非常感谢人们给出的答案。

最终,我真的很希望看到 Apple 允许我们(开发人员)检测何时按下了清除按钮。

很明显,按下它会被检测到,因为搜索框中的所有文本都会被清除。

我猜它现在在他们的优先事项列表中并不是很高...但我真的希望 Apple 有人能给 UISearchBar 一点爱!

【讨论】:

以上是关于UISearchbar clearButton 强制键盘出现的主要内容,如果未能解决你的问题,请参考以下文章

修改UITextField右侧clearButton的图片

ios中,如何设置clearbutton,且保证虚拟键盘不会一直弹出来

UITextField set placeholderColor and UITextField set clearButton Image

自定义 textField 的清除 button

如何在 SwiftUI 中向 Viewmodifiers 添加闭包?

UISearchBar