Styling the cancel button in a UISearchBar

47,658

Solution 1

What you want to do is pretty tough. There is no built-in hook to get at the cancel button.

However, there are a couple of options if you are willing to jimmy open the hood.

First off, UISearchBar is a UIView, and the Cancel button is also a view, which is added into the search bar as a subview, just as you would expect.

I have experimented a little, and can tell you that when the button is onscreen it has a size of 48,30.

So in viewWillAppear, you can do something like this:

  1. Find the cancel button view in [searchBar subviews] by looking for one with size 48,30. (There only seems to be one -- this could change...) You could be doubly careful and look for one that is in approximately the correct position (differs in landscape and portrait).

  2. Add a subview to the cancel button.

  3. The subview should be a UIControl (so that you can set enabled = NO, in order to make sure touch events get to the actual cancel button)

  4. It needs to have the right color and rounded corners; you will need to fudge the size for reasons I don't yet understand (55,30 seems to work)

  5. This will work if searchBar.showsCancelButton is always YES; if you want it to disappear when not editing the search string, you will need to find a hook to add the overlay each time the cancel button appears.

  6. As you can see, this is some ugly tinkering. Do it with eyes wide open.

Solution 2

You can use UIAppearance to style the cancel button without iterating subviews of the UISearchBar, but the UIButton header does not currently have any methods annotated with UI_APPEARANCE_SELECTOR.

EDIT: Drill down the subviews till you get that cancel button

But this usually returns nil until searchBar.setShowsCancelButton(true, animated: true) is called.

extension UISearchBar {

var cancelButton : UIButton? {
    if let view = self.subviews.first {
        for subView in view.subviews {
            if let cancelButton = subView as? UIButton {
                return cancelButton
            }
        }
    }
    return nil
}
}

Solution 3

In iOS 5.0+, you can use the appearnce proxy.

Before the search bar is showed.:

UIBarButtonItem *searchBarButton = [UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil];
[searchBarButton setBackgroundImage:myCancelButtonImageNormal forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[searchBarButton setBackgroundImage:myCancelButtonImageHighlighted forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
[searchBarButton setTitleTextAttributes:barButtonTitleTextAttributesNormal forState:UIControlStateNormal];
[searchBarButton setTitleTextAttributes:barButtonTitleTextAttributesHighlighted forState:UIControlStateHighlighted];

If you use [UIButton appearanceWhenContainedIn:[UISearchBar class], nil], it will affect other buttons (e.g. clear button). So, you'd better not use UIButton's appearnce. Try UIBarButtonItem.

Solution 4

Change the title of 'Cancel' button:

[[UIButton appearanceWhenContainedIn:[UISearchBar class], nil] setTitle:@"newTitle" forState:UIControlStateNormal];

Swift equivalent:

   let cancelButton = UIButton.appearance(whenContainedInInstancesOf: [UISearchBar.self])
   cancelButton?.setTitle("cancel".localized, for: .normal)

Solution 5

Though this might not be exactly relevant to the original question, the solution is still applicable in the larger sense of trying to customize the Cancel button in the UISearchBar. Thought this will help others who are stuck in such a scenario.

My situation was to change the cancel button's title, but with a twist, wherein I did not want to show the cancel button by default but only wanted it to show up, when the user enters the search mode (by clicking inside the search text field). At this instant, I wanted the cancel button to carry the caption "Done" ("Cancel" was giving a different meaning to my screen, hence the customization).

Nevertheless, here's what I did (a combination of caelavel's and Arenim's solutions):

Subclassed UISearchBar as MyUISearchBar with these two methods:

-(void) setCloseButtonTitle: (NSString *) title forState: (UIControlState)state
{
    [self setTitle: title forState: state forView:self];
}

-(void) setTitle: (NSString *) title forState: (UIControlState)state forView: (UIView *)view
{
    UIButton *cancelButton = nil;
    for(UIView *subView in view.subviews){
        if([subView isKindOfClass:UIButton.class])
        {
            cancelButton = (UIButton*)subView;
        }
        else
        {
            [self setTitle:title forState:state forView:subView];
        }
    }

    if (cancelButton)
        [cancelButton setTitle:title forState:state];

}

And in the viewcontroller which uses this Searchbar, the following piece of code takes care of showing the cancel button and customizing its title:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    MyUISearchBar *sBar = (MyUISearchBar *)searchBar;
    [sBar setShowsCancelButton:YES];
    [sBar setCloseButtonTitle:@"Done" forState:UIControlStateNormal];   
}

Strangely enough, I did not have to do anything to hide the cancel button, as it is hidden by default, when the search mode is exited.

Share:
47,658
Chu Yeow
Author by

Chu Yeow

http://blog.codefront.net/ I'm a Ruby and JavaScript guy most of the time.

Updated on December 17, 2020

Comments

  • Chu Yeow
    Chu Yeow over 3 years

    I have a UISearchBar that has a cancel button (it's displayed using -(void)setShowsCancelButton:animated). I've changed the tintColor of the search bar like this in an attempt to get a grayish searchbar:

    UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320, 40)];
    searchBar.tintColor = [UIColor colorWithWhite:0.8 alpha:1.0];
    

    This is what it looks like now - notice how the cancel button is also gray: http://twitpic.com/c0hte

    Is there a way to set the color of the cancel button separately so it looks more like this: http://twitpic.com/c0i6q