web-dev-qa-db-ja.com

UISearchbar clearButtonは、キーボードを強制的に表示します

テーブルビューのライブフィルターとして機能するUISearchBarがあります。 endEditing:を介してキーボードが閉じられると、クエリテキストと灰色の円形の「クリア」ボタンが残ります。ここから、灰色の「クリア」ボタンをタップすると、テキストがクリアされるとキーボードが再表示されます。

これを防ぐにはどうすればよいですか?キーボードが現在開いていない場合は、キーボードを再度開かずにそのボタンでテキストをクリアする必要があります。

クリアボタンをタップすると呼び出されるプロトコルメソッドがあります。ただし、UISearchBarにresignFirstResponderメッセージを送信しても、キーボードには影響しません。

44
Jens Kohl

これは古い質問であり、私はちょうど同じ問題に出会い、次の方法でそれを解決することができました:

searchBar:textDidChange:ユーザーが「クリア」ボタンをタップしたためにUISearchBarDelegateのメソッドが呼び出されます。searchBarはまだ最初のレスポンダーになっていないため、ユーザーが実際にクリアしようとしたときに検出するためにそれを利用できますsearchBarにフォーカスを移さずに、および/または他のことを行います。

これを追跡するには、viewControllerでsearchBarデリゲートでもあるBOOL ivarを宣言し(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;
}

基本的にはそれだけです。

ベスト

122
boliva

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];   
}
33

クリアボタンが押されたかどうかを知るための非常に安全な方法を見つけ、ユーザーが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を編集するときにクリアボタンをクリックすると、2つのpingがありますが、ユーザーが編集していないときにクリックすると1つだけになることです。


編集:私はそれをテストすることはできませんが、ここに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!")
    }
}
10
CyberDandy

@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
8
benvolioT

私の経験からの最善の解決策は、システムのクリアボタンの上に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];

}
3
Peter Lapisu

textDidChange UISearchBarDelegateメソッド内でsearchBar.resignFirstResponder()を呼び出しても、UISearchBarの「クリア」ボタンを押すと、キーボードが自動的に開きます。

「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.
        }
    }
}
1
luismgb

iOS 8以上でUISearchControllerを使用している場合は、単にUISearchControllerをサブクラス化する必要があります。完全を期すために、cancelボタンを非表示にすることもできます。これは、UISearchBarからテキストをクリアすると検索が事実上キャンセルされるためです。使用したい場合は、以下のコードを追加しました。

この利点は、UIViewControllerのサブクラスを必要とするのではなく、任意のクラスおよびビューでこれを使用できることです。このソリューションの最後に、UISearchControllerを初期化する方法も含めます。

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を独自のカテゴリに分割しました。これは、カテゴリによってクラスがよりクリーンで保守しやすくなるためです。メインクラスのインターフェイス/実装内にデリゲートを保持する場合は、大歓迎です。

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
mikeho

[クリア]ボタンをタッチすると、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
H K

検索バーのデリゲート呼び出しのうち、古い値から新しい値への変更を受け入れるように求められます-新しい値がnilであり、古い値が非nilであり、ユーザーがそうではなかったことを示すインジケーターを検出できますキーボードが最後に起動してから何でも入力しました-その場合、検索バーの最初のレスポンダーを辞任します。ただし、キーボードが一時的に表示されるかどうかはわかりません。

私は非常によく似た状況にあり、自分で試してみるかもしれません。

EndEditingメソッドで、UISearchBarをクリアしないのはなぜですか?最初のレスポンダーを辞任する場所でもあるはずなので、それは理にかなっています。

0
Corey Floyd

A Swift @bolivaの回答のバージョン。

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
    }
}
0
swordray