45

I'd like to change the text from "Cancel" to "Done" of the Cancel button inside the UISearchBar in iOS 8. I am using UISearchController. I've tried different approaches for iOS 6 and iOS 7 and they do not work. Has anybody done this?

jprofitt
  • 10,874
  • 4
  • 36
  • 46
Wilmer
  • 753
  • 1
  • 5
  • 9
  • possible duplicate of [UISearchController change 'Cancel' button title](http://stackoverflow.com/questions/28642807/uisearchcontroller-change-cancel-button-title) – Praveen Gowda I V Mar 22 '15 at 17:12
  • I have written an answer to this topic here: http://stackoverflow.com/questions/19206757/how-to-change-textcolor-of-cancel-button-of-uisearchbar-in-ios7. Just use the SHSearchBar Cocoapod which is not such a pain in the ass like the UISearchBar. – blackjacx Nov 25 '16 at 12:40
  • Don't hesitate to accept polo987's answer as the earliest of the claenest to date. – Anton Tropashko Jun 01 '20 at 12:23

14 Answers14

88

Objective-C:

[searchBar setValue:@"customString" forKey:@"_cancelButtonText"];

Swift:

searchBar.setValue("customString", forKey:"_cancelButtonText")
Burhanuddin Sunelwala
  • 5,318
  • 3
  • 25
  • 51
40

This worked for me in ios8 through ios13, did not try in ios7, but should do the trick, beware of placing this line in the early times of the app cycle (eg : appDelegate)

[[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTitle:@"Annuler"];

and as of ios9 you could also use

[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitle:@"Annuler"];

Swift version:

UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Annuler"

Hope that helps ;)

Anton Tropashko
  • 5,486
  • 5
  • 41
  • 66
polo987
  • 780
  • 5
  • 14
20

Found it!. in iOS 8, the cancel button is under the key "cancelButton". I used UISearchBar delegate searchBarTextDidBeginEditing to make the change.

func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
    let uiButton = searchBar.valueForKey("cancelButton") as UIButton
    uiButton.setTitle("Done", forState: UIControlState.Normal)

}
Wilmer
  • 753
  • 1
  • 5
  • 9
  • 5
    I would consider not setting the button title inside this callback. It seems a bit odd and unnecessary. Consider setting this value when you setup your view. – Steffen D. Sommer Apr 18 '15 at 10:46
  • 1
    100 times better than use private `_cancelButtonText ` – Leo Mar 20 '16 at 11:16
  • @SteffenD.Sommer It made sense for me to set it inside the callback because I'm not showing the Cancel button until the UISearchBar is in focus. – toddg May 11 '16 at 02:00
  • 4
    @Leo: `cancelButton` is also an undocumented private property. The absence of an underscore doesn't change that. – jamesk Jul 23 '16 at 11:36
  • in iOS 11.0 when the clicked for first time the *Cancel* button can not set. After the first time the text is changing as above. – eemrah Nov 13 '18 at 11:14
  • @elia If it still makes any sense, But it can be done if you make the searchBar as firstResponder in viewWillAppear – anamika41 Feb 19 '19 at 12:24
14

Swift 3.0:

In your AppDelegate add this:

UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "my text"
Maysam
  • 7,246
  • 13
  • 68
  • 106
9
 for view in (UISearchBar.subviews[0]).subviews{
             if let button = view as? UIButton{
             button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
             button.setTitle("Cancel", forState:.Normal)
            }
     }

self.subviews[0]).subviews contain UISearchBarBackground,UISearchBarTextField,UINavigationButto‌​n in which UINavigationButton is subclass of UIButton.Check for View as UIButton and if yes then change property of UIButton.

Donal
  • 6,082
  • 2
  • 19
  • 30
  • Please use the [edit] link to add this information to your post. Comments are “second-class citizens” and could be deleted at any time. – Jed Fox Nov 03 '16 at 10:26
  • This answer almost worked for me - I put the code in `searchBarTextDidBeginEditing` and it changed the button's title properly. It didn't work the first time when the "Cancel" button is shown though - I suspect it's being created a few moments later than the call for `searchBarTextDidBeginEditing`. I ended up using [this solution](http://stackoverflow.com/a/40257292/1540248) in my `viewDidLoad` method - works like a charm! – MayaLekova Feb 02 '17 at 11:16
8

I was having trouble getting this to work for a UISearchBar within a UISearchController. The text didn't change the first time the cancel button was shown but it did the second time.

That is until I saw @polo987's answer. Here's what I did that worked:

UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Done"
AamirR
  • 11,672
  • 4
  • 59
  • 73
toddg
  • 2,863
  • 2
  • 18
  • 33
8

For iOS 13 Support:

Swift:

    UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Whatever"

Objective C:

    [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:[NSArray arrayWithObject:[UISearchBar class]]] setTitle:@"Whatever"]
Vinoth
  • 119
  • 1
  • 6
6

In Swift:

searchBar.setValue("customString", forKey: "_cancelButtonText")

Based on Burhanuddin Sunelwala answer.

Aleksejs Mjaliks
  • 8,647
  • 6
  • 38
  • 44
  • 4
    This is a dangerous solution. If apple change the name of the key "_cancelButtonText" this will cause your app to crash. – Josh Apr 24 '18 at 10:10
6

In Swift4

Change Title:

(searchBar.value(forKey: "cancelButton") as! UIButton).setTitle("Done", for: .normal)

Change Color:

(searchBar.value(forKey: "cancelButton") as! UIButton).setTitleColor(UIColor.blue, for: .normal)

Akhil Clement
  • 575
  • 1
  • 7
  • 17
3

I have added titleLabel.font to the mix...

This goes straight into my ViewDidLoad() {

        let searchFont = UIFont(name: "BrandonGrotesque-Medium", size: 12)
        let textFieldSearchBar = self.searchBar.valueForKey("searchField") as! UITextField
        textFieldSearchBar.font = searchFont
        let cancelButton = searchBar.valueForKey("cancelButton") as! UIButton
        cancelButton.setTitle("Done", forState: .Normal)
        cancelButton.titleLabel!.font = searchFont
David West
  • 1,550
  • 1
  • 18
  • 31
3

Swift 4.2, 4.0+

A custom class to customize the most common elements in search bar.

class SearchBar: UISearchBar {

    private enum SubviewKey: String {
        case searchField, clearButton, cancelButton,  placeholderLabel
    }

    // Button/Icon images
    public var clearButtonImage: UIImage?
    public var resultsButtonImage: UIImage?
    public var searchImage: UIImage?

    // Button/Icon colors
    public var searchIconColor: UIColor?
    public var clearButtonColor: UIColor?
    public var cancelButtonColor: UIColor?
    public var capabilityButtonColor: UIColor?

    // Text
    public var textColor: UIColor?
    public var placeholderColor: UIColor?
    public var cancelTitle: String?

    // Cancel button to change the appearance.
    public var cancelButton: UIButton? {
        guard showsCancelButton else { return nil }
        return self.value(forKey: SubviewKey.cancelButton.rawValue) as? UIButton
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if let cancelColor = cancelButtonColor {
            self.cancelButton?.setTitleColor(cancelColor, for: .normal)
        }
        if let cancelTitle = cancelTitle {
            self.cancelButton?.setTitle(cancelTitle, for: .normal)
        }

        guard let textField = self.value(forKey: SubviewKey.searchField.rawValue) as? UITextField else { return }

        if let clearButton = textField.value(forKey: SubviewKey.clearButton.rawValue) as? UIButton {
            update(button: clearButton, image: clearButtonImage, color: clearButtonColor)
        }
        if let resultsButton = textField.rightView as? UIButton {
            update(button: resultsButton, image: resultsButtonImage, color: capabilityButtonColor)
        }
        if let searchView = textField.leftView as? UIImageView {
            searchView.image = (searchImage ?? searchView.image)?.withRenderingMode(.alwaysTemplate)
            if let color = searchIconColor {
                searchView.tintColor = color
            }
        }
        if let placeholderLabel =  textField.value(forKey: SubviewKey.placeholderLabel.rawValue) as? UILabel,
            let color = placeholderColor {
            placeholderLabel.textColor = color
        }
        if let textColor = textColor  {
            textField.textColor = textColor
        }
    }

    private func update(button: UIButton, image: UIImage?, color: UIColor?) {
        let image = (image ?? button.currentImage)?.withRenderingMode(.alwaysTemplate)
        button.setImage(image, for: .normal)
        button.setImage(image, for: .highlighted)
        if let color = color {
            button.tintColor = color
        }
    }
}

Usage:

class ViewController: UIViewController {

    @IBOutlet private weak var searchBar: SearchBar!

    override func viewDidLoad() {
        super.viewDidLoad()

        searchBar.clearButtonColor      = .purple
        searchBar.cancelButtonColor     = .magenta
        searchBar.searchIconColor       = .red
        searchBar.placeholderColor      = .green
        searchBar.textColor             = .orange
        searchBar.capabilityButtonColor = .green
    }
}

Result: enter image description here

Kamran
  • 14,987
  • 4
  • 33
  • 51
2

I know this may seem to be a bit irrelevant but in my opinion this is safer than using private apis and more efficient than taking a dive into the unknown views. This solution makes sense only if you have your own localisation engine and you do want Apple to follow your mechanism all over the app. Basically my idea is to switch the language the iOS SDK uses to localise the button by altering the "AppleLanguages" key in the NSUserDefaults. You can go here for more information about how this works.

[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:@"en", @"fr", nil] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];

Put this code to use the English localisation and a French fallback no matter what language is set on the phone. This only takes effect within the app.

Exception
  • 785
  • 8
  • 8
1

Here is Swift solution:

for (view) in _searchController.searchBar.subviews {
    for (subview) in view.subviews {
        if (subview.isKindOfClass(UIButton.self)) {
            let cancelButton = subview as! UIButton
            cancelButton.setTitle("BlaBla", forState: .Normal)
            break
        }
    }
}
pchelnikov
  • 425
  • 6
  • 6
-1

let uiButton = searchBar.value(forKey: "cancelButton") as! UIButton uiButton.setTitle(NSLocalizedString("Cancel", comment: ""), for: .normal)

Davender Verma
  • 503
  • 2
  • 12