UISearchBar disable auto disable of cancel button
Asked Answered
T

11

37

I have implemented a UISearchBar into a table view and almost everything is working except one small thing: When I enter text and then press the search button on the keyboard, the keyboard goes away, the search results are the only items shown in the table, the text stays in the UISearchBar, but the cancel button gets disabled.

I have been trying to get my list as close to the functionality of the Apple contacts app and when you press search in that app, it doesn't disable the cancel button.

When I looked in the UISearchBar header file, I noticed a flag for autoDisableCancelButton under the _searchBarFlags struct but it is private.

Is there something that I am missing when I setup the UISearchBar?

Tobietobin answered 3/12, 2010 at 17:51 Comment(0)
T
51

I found a solution. You can use this for-loop to loop over the subviews of the search bar and enable it when the search button is pressed on the keyboard.

for (UIView *possibleButton in searchBar.subviews)
{
    if ([possibleButton isKindOfClass:[UIButton class]])
    {
        UIButton *cancelButton = (UIButton*)possibleButton;
        cancelButton.enabled = YES;
        break;
    }
}
Tobietobin answered 3/12, 2010 at 19:10 Comment(5)
This did not work for me on iOS 6, as the only button present is a UINavigationButton which appears to be a private class.Andrey
Have you tried setting the showsCancelButton property on UISearchBar?Tobietobin
UINavigationButton is subclass if UIButton, so this code works, and there is no private class usage. github.com/kennytm/iphone-private-frameworks/blob/master/UIKit/…Vorticella
One just have to use it at the proper moment, which is after search bar lost focus (resignFirstResponder).Overshine
Works for me! I did ((UIButton*)possibleButton).enabled = YES; as I find the use of cancelButton too much.Scolecite
B
15

I had to tweak this a bit to get it work for me 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;
            }
        }
    }
}
Brummett answered 9/8, 2013 at 16:3 Comment(1)
You should really just return out of the inner if statement instead of breaking. That break statement is only going to break you out of the inner for loop and will continue to loop over the searchBar.subviews even after finding the cancel button.Tobietobin
N
5

There is two way to achieve this easily

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
    //  The small and dirty
    [(UIButton*)[searchBar valueForKey:@"_cancelButton"] setEnabled:YES];

    // The long and safe
     UIButton *cancelButton = [searchBar valueForKey:@"_cancelButton"];
    if ([cancelButton respondsToSelector:@selector(setEnabled:)]) {
         cancelButton.enabled = YES;
    }
}

You should go with the second one, it will not crash your application if Apple will change it in the background.

BTW i tested it from iOS 4.0 to 8.2 and no changes, also i use it in my Store approved application without any issues.

Niemeyer answered 12/1, 2015 at 22:25 Comment(2)
This works but your app may be rejected as it is using a private method. Beware.Deutschland
@PedroJoséCardoso no, this not really counts as private api usage, and i never saw any rejects because of this.Niemeyer
T
3

This is what made it to work on iOS 6 for me:

searchBar.showsCancelButton = YES;
searchBar.showsScopeBar = YES;
[searchBar sizeToFit];
[searchBar setShowsCancelButton:YES animated:YES];
Triform answered 1/11, 2012 at 2:11 Comment(1)
this does not solve the problem: the op want to disable the auto-disable feature, i.e. do not allow UISearchBar to disable it.Buoyage
R
3

Here's my solution, which works for all situations in all versions of iOS.

IE, other solutions don't handle when the keyboard gets dismissed because the user dragged a scroll view.

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

// This will handle whenever the text field is resigned non-programatically
// (IE, because it's set to resign when the scroll view is dragged in your storyboard.)
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
    [self performSelector:@selector(enableCancelButton:) withObject:searchBar afterDelay:0.001];
}

// Also follow up every [searchBar resignFirstResponder];
// with [self enableCancelButton:searchBar];
Rundown answered 13/1, 2014 at 5:39 Comment(0)
C
3

As per my answer here, place 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);
    });
}
Claret answered 5/9, 2014 at 12:53 Comment(0)
C
2

None of the answers worked for me at all. I'm targeting iOS 7. But I found an answer.

What I'm trying is something like the Twitter iOS app. If you click on the magnifying glass in the Timelines tab, the UISearchBar appears with the Cancel button activated, the keyboard showing, and the recent searches screen. Scroll the recent searches screen and it hides the keyboard but it keeps the Cancel button activated.

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 arrived at this solution by setting a breakpoint at my table view's scrollViewWillBeginDragging:. I looked into my UISearchBar and bared its subviews. It always has just one, which is of type UIView (my variable searchBarSubview).

enter image description here

Then, that UIView holds an NSArray called subviewCache and I noticed that the last element, which is the third, is of type UINavigationButton, not in the public API. So I set out to use key-value coding instead. I checked if the UINavigationButton responds to setEnabled:, and luckily, it does. So I set the property to @YES. Turns out that that UINavigationButton is the Cancel button.

This is bound to break if Apple decides to change the implementation of a UISearchBar's innards, but what the hell. It works for now.

Chekhov answered 25/2, 2014 at 17:20 Comment(0)
S
2

Here's a Swift 3 solution that makes use of extensions to get the cancel button easily:

extension UISearchBar {
    var cancelButton: UIButton? {
        for subView1 in subviews {
            for subView2 in subView1.subviews {
                if let cancelButton = subView2 as? UIButton {
                    return cancelButton
                }
            }
        }
        return nil
    }
}

Now for the usage:

class MyTableViewController : UITableViewController, UISearchBarDelegate {

    var searchBar = UISearchBar()

    func viewDidLoad() {
        super.viewDidLoad()
        searchBar.delegate = self
        tableView.tableHeaderView = searchBar
    }

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        DispatchQueue.main.async {
            if let cancelButton = searchBar.cancelButton {
                cancelButton.isEnabled = true
                cancelButton.isUserInteractionEnabled = true
            }
        }
    }
}
Sanitarium answered 7/4, 2017 at 23:20 Comment(2)
Cleanest solution I've seen. Thanks.Madaras
Doesn't work on iOS 13+, as subView2 is not a UIButtonAlegar
J
1

For Monotouch or Xamarin iOS I have the following C# solution working for iOS 7 and iOS 8:

foreach(UIView view in searchBar.Subviews)
{
    foreach(var subview in view.Subviews)
    {
        //Console.WriteLine(subview.GetType());
        if(subview.GetType() == typeof(UIButton))
        {
            if(subview.RespondsToSelector(new Selector("setEnabled:")))
            {
                UIButton cancelButton = (UIButton)subview;
                cancelButton.Enabled = true;
                Console.WriteLine("enabledCancelButton");
                return;
            }
        }
    }
}

This answer is based on David Douglas solution.

Jessie answered 9/1, 2015 at 14:31 Comment(0)
U
1

A more complete answer:

  • since iOS 7, there is an additional level of subviews under the searchBar
  • a good place to enable the cancel button is in searchBarTextDidEndEditing

.

extension MyController: UISearchBarDelegate {
  public func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
    DispatchQueue.main.async {
    // you need that since the disabling will
    // happen after searchBarTextDidEndEditing is called
      searchBar.subviews.forEach({ view in
        view.subviews.forEach({ subview in
          // ios 7+
          if let cancelButton = subview as? UIButton {
            cancelButton.isEnabled = true
            cancelButton.isUserInteractionEnabled = true
            return
          }
        })
        // ios 7-
        if let cancelButton = subview as? UIButton {
          cancelButton.isEnabled = true
          cancelButton.isUserInteractionEnabled = true
          return
        }
      })
    }
  }
}
Ubangishari answered 4/12, 2016 at 4:5 Comment(0)
S
0

Time passes, but the problem is still there...

Elegant Swift 5/iOS 13 solution:

func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
    for case let cancelButton as UIButton in searchBar.subviews {
        cancelButton.isEnabled = true
    }
}
Scharf answered 17/6, 2020 at 5:25 Comment(1)
In iOS 13 UISearchBar.subviews only returns one element, and it's not a UIButtonAlegar

© 2022 - 2024 — McMap. All rights reserved.