19

I'm using the UISearchBar (but not the SearchDisplayController that's typically used in conjunction) and I'd like to dismiss the keyboard when you hit the 'X' button.

I've followed TomSwift's suggestion on getting called when the 'X' is tapped and that works great. But resigning first responder from the text field and also invoking in the UISearchBar instance, both with resignFirstResponder, won't make the keyboard go away.

Is there a way to get rid of the keyboard when the user has tapped the X button?

Here's what I did to get the 'Clear' notify:

- (void)viewDidLoad:
{
    for (UIView* v in searchBar.subviews)
    {
        if ( [v isKindOfClass: [UITextField class]] )
        {
            UITextField *tf = (UITextField *)v;
            tf.delegate = self;
            break;
        }
    }    
}

Then I have my class setup to implement both UISearchBarDelegate and UITextFieldDelegate.

Having the class serve as the textfield delegate allows me to get this call:

- (BOOL)textFieldShouldClear:(UITextField *)textField
{
     [textField resignFirstResponder];
     [self.searchBar resignFirstResponder];
     return YES;
}

I've tried everything I can think of. The last thing I'm trying is to find a way to issue the 'searchBarCancelButtonClicked' that UISearchDelegate will invoke on my Controller class but not I'm sure how I could do this since the UISearchBar doesn't seem to have any direct methods to invoke with this name.

Community
  • 1
  • 1
Justin Galzic
  • 3,951
  • 6
  • 28
  • 39

14 Answers14

72

Toms answer got me thinking. If it is that the search bar is not yet the firstResponder when the user clicks the clear button we can just wait until it is, and then have it resignFirstResponder; i.e. along the lines of:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
  [self performFilteringBySearchText: searchText]; // or whatever

  // The user clicked the [X] button or otherwise cleared the text.
  if([searchText length] == 0) {
    [searchBar performSelector: @selector(resignFirstResponder) 
                    withObject: nil 
                    afterDelay: 0.1];
  }
}

Works like a charm, and less hacky than Tom's IMHO.

radiospiel
  • 2,450
  • 21
  • 28
  • Awesome sauce! A counter intuitive solution, but i'm nonetheless using it loud and proud. Thanks – Joel Balmer Mar 26 '14 at 15:07
  • Using GCD: dispatch_async(dispatch_get_main_queue(), ^(void){ [self.searchBar resignFirstResponder]; }); – maxhs Jan 06 '15 at 05:30
  • What happens when the user hits the delete button ? in that case the length of the searchText would be 0 – Rodrigo Gonzalez Feb 06 '16 at 20:04
  • @RodrigoGonzalez I honestly don't what happens on delete. I think in that case the search text should be cleared, and the keyboard disappear. Is this not what everyone wants? – radiospiel Feb 08 '16 at 14:21
  • I am confused though searchBar.endEditing(true) works on done/search button and cancel button but not on (X). Why ? anyone – Alok C Sep 16 '16 at 04:57
  • I think that there is some kind of race condition between several parts of the UI code that manages the on screen keyboard. I guess in processing the close button UIKit wants to make the searchBar the firstResponder, which makes the beyboard visisble (since it is an event that appears in there). Done/Search, on the other hand, would in general not activate the search bar. Having said that I might add that this is just moot speculation. – radiospiel Sep 22 '16 at 05:38
6

This works:

[searchBar performSelector:@selector(resignFirstResponder) withObject:nil afterDelay:0.1];
pickwick
  • 3,134
  • 22
  • 30
6

Updated for SWIFT 3:

Suppose that the user has entered a string in searchfield and clicks x the following code works to hide keyboard when x is pressed

`

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) 
{
            if searchBar.text == nil || searchBar.text == ""
            {
                searchBar.perform(#selector(self.resignFirstResponder), with: nil, afterDelay: 0.1)
            }
 }

`

Ammad
  • 341
  • 6
  • 7
3

Swift 2 version:

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
        // The user clicked the [X] button or otherwise cleared the text.
        if (searchText.characters.count == 0) {
            searchBar.performSelector("resignFirstResponder", withObject: nil, afterDelay: 0.1)
        }
    }
powertoold
  • 1,593
  • 13
  • 19
2

You can resignFirstResponder on the click of cancel Button as.

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

{

    SearchBar.showsCancelButton =NO;

}


- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{

    [SearchBar resignFirstResponder];


}
Sabby
  • 2,586
  • 2
  • 27
  • 41
  • 1
    But if you want to do this on x Button click,then you have to take a button and add the image like x to it.then on the click of this Button you can resign the FirstResponder – Sabby Nov 16 '10 at 04:35
  • This isn't exactly what he's looking for. He wants to remove the keyboard when the clear text button is clicked. – Tozar Nov 16 '10 at 04:40
  • Yes Tozer I know what he is looking for ,so just see the comment below which I have given.He needs to take his own button to do that in place of clear button. – Sabby Nov 16 '10 at 04:43
  • There's no Cancel button at all - just the Clear action from the X button and I don't want to add any other buttons. – Justin Galzic Nov 16 '10 at 23:52
  • 2
    There is cancel button which can be hidden or shown and clear button in the search bar field as well bro. – Sabby Nov 17 '10 at 04:14
2

Update:

Well, this is a total hack but I was able to make it work. Basically the code invokes the handler for the cancel button. To make it work I had to invoke the selector with a delay, and I'm not sure why this had to be. Also, I had to write an accessor for the cancel button just like you did for the text field.

Again, this is a total hack. I'm not sure I'd do this myself in an app.

// this in the context of the search bar
- (UIButton*) cancelButton
{
    for (UIView* v in self.subviews)
    {
        if ( [v isKindOfClass: [UIButton class]] )
            return (UIButton*)v;
    }

    return nil;
}

// this is the textField delegate callback
- (BOOL)textFieldShouldClear:(UITextField *)textField
{
    [textField resignFirstResponder];

    UIButton* cb = _searchBar.cancelButton;

    NSObject* target = [[cb allTargets] anyObject];

    NSArray* actions = [cb actionsForTarget: target forControlEvent:UIControlEventTouchUpInside];

    NSString* selectorName = [actions  objectAtIndex:0];

    SEL selector = NSSelectorFromString( selectorName );

    [target performSelector: selector withObject: cb afterDelay: 0.1];

    return YES;
}

Original answer:

How do you get the clear 'X' button to display in the first place? In my test case I dont see it displaying...

Try doing a resignFirstResponder on the searchBar, not the textField.

TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • I added the UISearchBar with Interface Builder and the X button shows up automatically after I've entered in a character into the Search Bar text field. I tried resigning both the searchBar (stored in the Controller class) and textfield. No luck – Justin Galzic Nov 17 '10 at 00:37
  • Okay, yeah I was being daft. I got that to work. Not sure why it's not dismissing, yet. – TomSwift Nov 17 '10 at 01:18
  • Oh it's the delay that seems to do it. With all of the hacking, I decided that trying to short circuit the SDK painful and went with adding the Cancel button. I'll have to do it the way the SDK intended. – Justin Galzic Nov 17 '10 at 16:13
1

I used a combination of @radiospiel's answer and also the answer that @Tozar linked to:

@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
Community
  • 1
  • 1
benvolioT
  • 4,507
  • 2
  • 36
  • 30
1

Shouldn't UI changes be made on the main thread instead of using performselector:WithObject:afterDelay:?

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if (searchText.length == 0) {
        [searchBar performSelectorOnMainThread:@selector(resignFirstResponder) withObject:nil waitUntilDone:NO];
    }
}
Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
Luca Gobbo
  • 41
  • 3
1

I found this in a previous question:

UISearchbar clearButton forces the keyboard to appear

It should do exactly what you want to do.

Community
  • 1
  • 1
Tozar
  • 976
  • 9
  • 20
1

Try to avoid

- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar

method in your code we can solve this

Ganesh
  • 1,059
  • 1
  • 10
  • 28
0

EDIT: Actually, the below breaks the delegate that is connected to UISearchBar. Just subclass UISearchBar and overwrite the UITextField Delegate method.

===========================

I had to do this to get access to the UITextView

for (UIButton* v in [self.searchBar.subviews[0] subviews])
{
    if ( [v isKindOfClass: [UITextField class]] )
    {
        UITextField *tf = (UITextField *)v;
        tf.delegate = self;
        break;
    }
}
Rich Fox
  • 2,194
  • 18
  • 20
0

Credit to Maxhs for original answer : This is swift 2.2 version : Work like charm for me

if searchBar.text == "" {
                dispatch_async(dispatch_get_main_queue(), { 
                    self.searchBar.resignFirstResponder()
                })
            }
Alok C
  • 2,787
  • 3
  • 25
  • 44
0

Another point of view for clear text flow (similar to @TomSwift answer but clearer for me and less tricky). Also, I need to hide Cancel button after exit from the search bar, implement live search (after each symbol) and cover table before user complete search.

//self.searchHoverView can cover table view
//performSearchWithSearchBar: method for performing search

#pragma mark - UISearchBarDelegate

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
    [searchBar setShowsCancelButton:YES animated:YES];
    self.searchHoverView.hidden = NO;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (searchText.length) {
        [self performSearchWithSearchBar:searchBar];
    } else {
        UIButton *button;
        for (UIView *subView in [searchBar subviews]) {
            for (UIView *view in [subView subviews]) {
                if ([view isKindOfClass:[UIButton class]]) {
                    button = (UIButton *)view;
                    break;
                }
            }
        }

        if (button) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [button sendActionsForControlEvents:UIControlEventTouchUpInside];
            });
        }
    }
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    self.searchBar.text = nil;

    [self.searchBar setShowsCancelButton:NO animated:YES];
    [self.searchBar resignFirstResponder];

    self.searchHoverView.hidden = YES;
}
WINSergey
  • 1,977
  • 27
  • 39
0

When you click the 'x' button, the string in the search bar text field becomes an empty collection of characters.

Checking the length of the collection helps to detect when the 'x' button has been clicked.

I had a similar issue and this solution worked for me:

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    //let searchText = searchText.trimmingCharacters(in: .whitespaces)
    if searchText.isEmpty{
        DispatchQueue.main.async { [weak self] in
            guard let self = self else{ return }
            self.searchBar.resignFirstResponder()
        }
    }
}
tomisin
  • 1
  • 1