How to enable cancel button using UISearchBar?

In the application for contacts on the iPhone, if you enter a search query, click the "Search" button, the keyboard is hidden, BUT the cancel button is still on. In my application, the cancel button is disabled when I call resignFirstResponder.

Does anyone know how to hide the keyboard while keeping the cancel button in the enabled state?

I am using the following code:

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

The keyboard is pushed out of view, but the Cancel button to the right of the search text box is disabled, so I cannot cancel the search. The contacts application supports the cancel button in the enabled state.

I think maybe one of the solutions is to dive into the searchBar object and call resignFirstResponder in the real text field, and not in the search bar.

Any input is appreciated.

+49
iphone uisearchbar cancel-button
Apr 01 2018-12-12T00:
source share
18 answers

try it

 for(id subview in [yourSearchBar subviews]) { if ([subview isKindOfClass:[UIButton class]]) { [subview setEnabled:YES]; } } 
+21
Apr 01 2018-12-12T00:
source share

This method worked in iOS7.

 - (void)enableCancelButton:(UISearchBar *)searchBar { for (UIView *view in searchBar.subviews) { for (id subview in view.subviews) { if ( [subview isKindOfClass:[UIButton class]] ) { [subview setEnabled:YES]; NSLog(@"enableCancelButton"); return; } } } } 

(Also make sure to call it anywhere after using [_searchBar resignFirstResponder].)

+29
Aug 09 '13 at 15:53
source share

The decision made will not work when you start scrolling through the table, and do not click the "Search" button. In this case, the Cancel button will be disabled.

This is my solution that re-enables the Cancel button every time I disconnect using KVO.

 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Search for Cancel button in searchbar, enable it and add key-value observer. for (id subview in [self.searchBar subviews]) { if ([subview isKindOfClass:[UIButton class]]) { [subview setEnabled:YES]; [subview addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew context:nil]; } } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Remove observer for the Cancel button in searchBar. for (id subview in [self.searchBar subviews]) { if ([subview isKindOfClass:[UIButton class]]) [subview removeObserver:self forKeyPath:@"enabled"]; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // Re-enable the Cancel button in searchBar. if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:@"enabled"]) { UIButton *button = object; if (!button.enabled) button.enabled = YES; } } 
+10
Sep 13 '12 at 12:21
source share

Like iOS 6, instead of UIButton, the button looks like a UINavigationButton (private class).

I modified the above example to look like this.

 for (UIView *v in searchBar.subviews) { if ([v isKindOfClass:[UIControl class]]) { ((UIControl *)v).enabled = YES; } } 

However, it is obviously fragile, as we are deceiving the insides. It may also include more than a button, but it works for me until a better solution is found.

We must ask Apple to expose this.

+9
Oct 26
source share

It seemed to work for me (in viewDidLoad):

 __unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self]; 

I understand that I should probably use the UISearchDisplayController correctly, but this was a simple fix for my current implementation.

+8
01 Oct '13 at 21:14
source share

You can use the runtime API to access the cancel button.

 UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"]; [btnCancel setEnabled:YES]; 
+7
Jul 13 '15 at 10:45
source share

I expanded on what others have already posted here, implementing this as a simple category in the UISearchBar.

UISearchBar + alwaysEnableCancelButton.h

 #import <UIKit/UIKit.h> @interface UISearchBar (alwaysEnableCancelButton) @end 

UISearchBar + alwaysEnableCancelButton.m

 #import "UISearchBar+alwaysEnableCancelButton.h" @implementation UISearchBar (alwaysEnableCancelButton) - (BOOL)resignFirstResponder { for (UIView *v in self.subviews) { // Force the cancel button to stay enabled if ([v isKindOfClass:[UIControl class]]) { ((UIControl *)v).enabled = YES; } // Dismiss the keyboard if ([v isKindOfClass:[UITextField class]]) { [(UITextField *)v resignFirstResponder]; } } return YES; } @end 
+5
Jan 25 '13 at 19:29
source share

Here is a slightly more robust solution that works on iOS 7. It will recursively traverse all subzones of the search bar to make sure that it includes all UIControl (including the Cancel button).

 - (void)enableControlsInView:(UIView *)view { for (id subview in view.subviews) { if ([subview isKindOfClass:[UIControl class]]) { [subview setEnabled:YES]; } [self enableControlsInView:subview]; } } 

Just call this method right after calling [self.searchBar resignFirstResponder] as follows:

 [self enableControlsInView:self.searchBar]; 

Voila! The cancel button remains on.

+4
Sep 26 '13 at 23:29
source share
 for (UIView *firstView in searchBar.subviews) { for(UIView* view in firstView.subviews) { if([view isKindOfClass:[UIButton class]]) { UIButton* button = (UIButton*) view; [button setEnabled:YES]; } } } 
+3
Mar 04 '14 at 7:14
source share

I found a different approach for it to work in iOS 7.

What I'm trying is similar to the Twitter iOS app. If you click on the magnifying glass on the Timelines tab, a UISearchBar appears with the Cancel button activated, a keyboard and the last search screen. Scroll through the last search screen and it hides the keyboard, but it holds the Cancel button.

This is my working code:

 UIView *searchBarSubview = self.searchBar.subviews[0]; NSArray *subviewCache = [searchBarSubview valueForKeyPath:@"subviewCache"]; if ([subviewCache[2] respondsToSelector:@selector(setEnabled:)]) { [subviewCache[2] setValue:@YES forKeyPath:@"enabled"]; } 

I came to this solution by setting a breakpoint in my scrollViewWillBeginDragging: table scrollViewWillBeginDragging: . I looked into my UISearchBar and exposed its subtitles. It always has only one that is of type UIView (my searchBarSubview variable).

enter image description here

Then, that UIView contains an NSArray called subviewCache , and I noticed that the last third element is of type UINavigationButton , and not a public API. Therefore, I decided to use the encoding with the key. I checked if the UINavigationButton responds to setEnabled: and, fortunately, it does. So I set the @YES property. It turns out that UINavigationButton is the Cancel button.

It will definitely break if Apple decides to change the implementation of the UISearchBar internally, but what the hell. He is working now.

+2
Feb 25 '14 at 17:26
source share

SWIFT version for David Douglas answer (tested on iOS9)

 func enableSearchCancelButton(searchBar: UISearchBar){ for view in searchBar.subviews { for subview in view.subviews { if let button = subview as? UIButton { button.enabled = true } } } } 
+2
Nov 05 '15 at 6:11
source share

Based on the smileyborg answer , just put this in your searchBar delegate:

 - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { dispatch_async(dispatch_get_main_queue(), ^{ __block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *); void (^ensureCancelButtonRemainsEnabled)(UIView *); weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) { for (UIView *subview in view.subviews) { if ([subview isKindOfClass:[UIControl class]]) { [(UIControl *)subview setEnabled:YES]; } weakEnsureCancelButtonRemainsEnabled(subview); } }; ensureCancelButtonRemainsEnabled(searchBar); }); } 

This solution works well on iOS 7 and above.

+1
Sep 05 '14 at 7:00
source share

You can create your CustomSearchBar that inherits from UISearchBar and implement this method:

 - (void)layoutSubviews { [super layoutSubviews]; @try { UIView *baseView = self.subviews[0]; for (UIView *possibleButton in baseView.subviews) { if ([possibleButton respondsToSelector:@selector(setEnabled:)]) { [(UIControl *)possibleButton setEnabled:YES]; } } } @catch (NSException *exception) { NSLog(@"ERROR%@",exception); } } 
0
Mar 03 '15 at 20:55
source share

The best decision

 [UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES; 
0
October 20 '16 at 10:34
source share

For iOS 10, Swift 3:

 for subView in self.movieSearchBar.subviews { for view in subView.subviews { if view.isKind(of:NSClassFromString("UIButton")!) { let cancelButton = view as! UIButton cancelButton.isEnabled = true } } } 
0
Jan 09 '17 at 7:47 on
source share

Best and easiest method:

 [(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES]; 
0
Jun 22 '17 at 5:29 on
source share

For iOS 9/10 (verified), Swift 3 (shorter):

 searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true }) 
0
Jul 05 '17 at 10:43 on
source share

Most hosted solutions are not reliable and will allow you to disable the "Cancel" button under various circumstances.

I tried to implement a solution in which the Cancel button is always turned on, even when you do more complex things using the search bar. This is implemented as a custom subclass of UISearchView in Swift 4. It uses the (forKey :) trick value to find the cancel button and search text field, and listens when the search field stops editing and turns on the cancel button again. It also allows a cancel button when the showCancelButton flag is switched.

It contains a couple of statements warning you when the internal details of the UISearchBar change and do not allow work.

 import UIKit final class CancelSearchBar: UISearchBar { override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { guard let searchField = value(forKey: "_searchField") as? UIControl else { assertionFailure("UISearchBar internal implementation has changed, this code needs updating") return } searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd) } override var showsCancelButton: Bool { didSet { enableSearchButton() } } @objc private func enableSearchButton() { guard showsCancelButton else { return } guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else { assertionFailure("UISearchBar internal implementation has changed, this code needs updating") return } cancelButton.isEnabled = true } } 
0
Dec 03 '17 at 11:32
source share



All Articles