I am developing an Application where I wanted to change the text of Search String in the SearchBar. I wanted to change the text of Cancel Button Also which appears next to the SearchBar. Before entering any string in the search bar we wil get the Search String as the default string. I wanted to change the text of that string and when we click on that searchbar we get a cancel button next to searchbar and I wanted to change the text of that cancel button.
15 Answers
Use the appearance proxy:
id barButtonAppearanceInSearchBar = [UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil];
[barButtonAppearanceInSearchBar setBackgroundImage:grayBackgroundImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[barButtonAppearanceInSearchBar setTitleTextAttributes:@{
NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-CondensedBold" size:20],
NSForegroundColorAttributeName : [UIColor blackColor]
} forState:UIControlStateNormal];
[barButtonAppearanceInSearchBar setTitle:@"X"];

- 3,133
- 1
- 29
- 31

- 13,273
- 1
- 38
- 44
-
It works. but it makes other `UIBarButtonItem`s not in an `UISearchBar` unable to display their titles in iOS5.x – Eric Chai Apr 29 '13 at 14:57
-
11setTitle: fails on iOS7 for this piece of code on Xcode 5 from today – Cœur Sep 11 '13 at 11:43
-
Do you have some fix for this on iOS 7? – gklka Sep 22 '13 at 17:10
-
The fix is to find the button in the subviews and call [button setTitle:@"Done" forState:UIControlStateNormal];. Did you try calling this method on the Appearance Proxy? – Yariv Nissim Sep 26 '13 at 22:20
-
In iOS9: [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]].title = @"Back"; – Soul Clinic Sep 26 '15 at 15:19
-
@SoulClinic, feel free to update the answer w/ iOS 9 support – Yariv Nissim Sep 26 '15 at 20:27
-
I think this is a better solution. – Berker Soyluoglu Mar 18 '16 at 10:02
You also need to have the "searchBar setShowsCancelButton" before the procedure.
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
[theSearchBar setShowsCancelButton:YES animated:NO];
for (UIView *subView in theSearchBar.subviews){
if([subView isKindOfClass:[UIButton class]]){
[(UIButton*)subView setTitle:@"Done" forState:UIControlStateNormal];
}
}
}
Note also: use UIButton to avoid problems with Apple!

- 26,607
- 8
- 71
- 97

- 392
- 3
- 2
-
1UIButton.class is an invalid use of the dot syntax, use [UIButton class]. – Grant Paul Jun 30 '10 at 05:39
-
You MAY also need to place it in this method too:- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller – Matt Dec 30 '11 at 23:51
-
3don't use this hack anymore, use UIAppearance. See more here http://stackoverflow.com/a/14509280/296649 – Tankista Apr 03 '13 at 14:06
-
3This worked great in iOS6 but you have to go one layer deeper in iOS7. Change the line: for (UIView *subView in theSearchBar.subviews){ to for (UIView *subView in [[personNameField.subviews objectAtIndex:0] subviews]){ Also note, you can easily check if you're on iOS7 by putting a NSString *version = [[UIDevice currentDevice] systemVersion]; if ([version hasPrefix:@"7."]) { – James Paul Mason Sep 24 '13 at 05:53
-
If I set title this way and if the title is very long, say `@"Very Long Title For Search Button"`, then it gets truncated from the middle. Does anybody know if we can set frame for SearchBar button as well? – nefarianblack Jun 17 '15 at 13:21
Solution for iOS 7. All credits for this go to Mr. Jesper Nielsen - he wrote the code.
-(void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
UIButton *cancelButton;
UIView *topView = theSearchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(@"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
[cancelButton setTitle:@"YourTitle" forState:UIControlStateNormal];
}
}

- 121
- 9

- 473
- 1
- 4
- 12
-
1This is works prefect from me in iOS 7. Jesper Nielsen and @SGI +1 – Naga Harish M Sep 23 '13 at 08:13
-
Where are you guys putting this? I keep seeing the "cancel" button first and then it changes to done. – AdamG Oct 14 '13 at 03:41
-
-
1doesn't work in iOS7. topView has 2 subviews: `UISearchBarBackground` and `UISearchBarTextField` – Nikita Took Nov 07 '13 at 14:50
-
@user2759361 Are you sure you put the code in `searchDisplayControllerWillBeginSearch` method? 'Cancel' button is added only when it needs to be displayed. – SGI Nov 08 '13 at 10:53
-
I'm having the same issue with it still displaying as "Cancel". Here is the exact code that I'm using:`- (void)searchDisplayControllerWillBeginSearch:-(UISearchDisplayController *)controller { UIButton *cancelButton; UIView *topView = theSearchBar.subviews[0]; for (UIView *subView in topView.subviews) { if ([subView isKindOfClass:NSClassFromString(@"UINavigationButton")]) { cancelButton = (UIButton*)subView; } } if (cancelButton) { [cancelButton setTitle:@"YourTitle" forState:UIControlStateNormal]; } }` – Nick Nov 09 '13 at 19:24
-
1It works if you put the code in searchDisplayControllerDidBeginSearch method. – Vishal Chaudhry Nov 11 '13 at 22:32
-
Here's one more way to implement this. Works ok in my app (already in AppStore): http://pastebin.com/9yaMWcdC – SGI Nov 12 '13 at 08:05
-
Well, a stupid question: are you sure you set your ViewController as a
and also set yourSearchBar.delegate? Above methods (like searchBarTextDidBeginEditing) are delegate methods. – SGI Nov 14 '13 at 12:31 -
I do not understand why this doesnt work for me. Do you use this with UISearchBarDelegate? I have set the delegate but still doesn't work. – SleepNot Mar 18 '14 at 09:29
-
It doesn't work on iOS7 both on searchDisplayControllerWillBeginSearch and searchDisplayControllerDidBeginSearch – DrKey Apr 24 '14 at 14:57
If by "Search String", you mean the placeholder, then this should do it:
[searchBar setPlaceholder:@"Whatever you want"];
As for changing the text of the cancel button, that may be a bit more difficult. Apple does not use a standard UIBarButtonItem for this, or even a non-standard UIButton. Instead they use a UINavigationButton for the cancel button in the search bar. Since this is not a documented public class, attempting to modify it could very well get your app rejected from the App Store. If you do want to risk rejection, then you could search through the subviews of searchBar:
for(UIView *view in [searchBar subviews]) {
if([view isKindOfClass:[NSClassFromString(@"UINavigationButton") class]]) {
[(UIBarItem *)view setTitle:@"Whatever you want"];
}
}
Note that the cancel button is loaded lazily, so you will have to do this modification when the search bar is activated by the user.

- 6,410
- 2
- 22
- 26
-
It won't work until after the search bar is activated. Like I mentioned above, the cancel button is loaded lazily, so it doesn't exist until after the search field is tapped. You can use the delegate methods for UISearchDisplayController to get around this problem. – glorifiedHacker Apr 20 '10 at 22:06
-
Note: you don't have to call -class on a Class object from NSClassFromString, only from classes you reference by name alone. – Grant Paul Jun 30 '10 at 05:38
-
You're right... I originally had [UINavigationButton class] in my code for testing, but since this would risk App Store rejection, I substituted the NSClassFromString() call and forgot to remove the class message. – glorifiedHacker Oct 15 '10 at 17:05
In iOS 7 if you are using UISearchBar just write this code in searchBarTextDidBeginEditing: method
searchBar.showsCancelButton = YES;UIView* view=searchBar.subviews[0];
for (UIView *subView in view.subviews) {
if ([subView isKindOfClass:[UIButton class]]) {
UIButton *cancelButton = (UIButton*)subView;
[cancelButton setTitle:@"إلغاء" forState:UIControlStateNormal];
}
}

- 1,104
- 15
- 24
I would like to fix the UIAppearance technique, as yar1vn code won't work with Xcode 5. With the following you will have code that works perfectly for both iOS 6 and iOS 7.
First, you need to understand that the cancel button is a private UINavigationButton:UIButton. Hence, it is not an UIBarButtonItem. After some inspection, it appears that UINavigationButton will respond to those UIAppearance selectors:
// inherited from UINavigationButton
@selector(setTintColor:)
@selector(setBackgroundImage:forState:style:barMetrics:)
@selector(setBackgroundImage:forState:barMetrics:)
@selector(setTitleTextAttributes:forState:)
@selector(setBackgroundVerticalPositionAdjustment:forBarMetrics:)
@selector(setTitlePositionAdjustment:forBarMetrics:)
@selector(setBackButtonBackgroundImage:forState:barMetrics:)
@selector(setBackButtonTitlePositionAdjustment:forBarMetrics:)
@selector(setBackButtonBackgroundVerticalPositionAdjustment:forBarMetrics:)
// inherited from UIButton
@selector(setTitle:forState:)
Coincidentally, those selectors match those of a UIBarButtonItem. Meaning the trick is to use two separate UIAppearance to handle the private class UINavigationButton.
/* dual appearance technique by Cœur to customize a UINavigationButton */
Class barClass = [UISearchBar self];
UIBarButtonItem<UIAppearance> *barButtonItemAppearanceInBar = [UIBarButtonItem appearanceWhenContainedIn:barClass, nil];
[barButtonItemAppearanceInBar setTintColor:...];
[barButtonItemAppearanceInBar setBackgroundImage:... forState:... style:... barMetrics:...];
[barButtonItemAppearanceInBar setBackgroundImage:... forState:... barMetrics:...];
[barButtonItemAppearanceInBar setTitleTextAttributes:... forState:...];
[barButtonItemAppearanceInBar setBackgroundVerticalPositionAdjustment:... forBarMetrics:...];
[barButtonItemAppearanceInBar setTitlePositionAdjustment:... forBarMetrics:...];
[barButtonItemAppearanceInBar setBackButtonBackgroundImage:... forState:... barMetrics:...];
[barButtonItemAppearanceInBar setBackButtonTitlePositionAdjustment:... forBarMetrics:...];
[barButtonItemAppearanceInBar setBackButtonBackgroundVerticalPositionAdjustment:... forBarMetrics:...];
UIButton<UIAppearance> *buttonAppearanceInBar = [UIButton appearanceWhenContainedIn:barClass, nil];
[buttonAppearanceInBar setTitle:... forState:...];
Now, this technique works for the Cancel button, but it also works for the Back button if you change the barClass to [UINavigationBar self]
.

- 37,241
- 25
- 195
- 267
This solution work for me - iOs7 and iOs8:
@interface ... : ...
@property (strong, nonatomic) IBOutlet UISearchBar *search;
@end
and
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
[searchBar setShowsCancelButton:YES animated:YES];
NSArray *searchBarSubViews = [[self.search.subviews objectAtIndex:0] subviews];
UIButton *cancelButton;
for (UIView *subView in searchBarSubViews) {
if ([subView isKindOfClass:NSClassFromString(@"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
break;
}
}
if (cancelButton) {
[cancelButton setTitle:@"New cancel" forState:UIControlStateNormal];
}
//insert this two lines below if you have a button appearance like this "Ne...cel"
[searchBar setShowsCancelButton:NO animated:YES];
[searchBar setShowsCancelButton:YES animated:YES];
}

- 530
- 1
- 7
- 16
On iOS 7, if you've set displaysSearchBarInNavigationBar = YES
on UISearchDisplayController
, replacing the cancel button title via subview recursion or the appearance proxy will not work.
Instead, use your own bar button in viewDidLoad
:
- (void)viewDidLoad
{
[super viewDidLoad];
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"A Custom Title", nil)
style:UIBarButtonItemStyleBordered
target:self
action:@selector(cancelButtonTapped:)];
// NB: Order is important here.
// Only do this *after* setting displaysSearchBarInNavigationBar to YES
// as that's when UISearchDisplayController creates it's navigationItem
self.searchDisplayController.navigationItem.rightBarButtonItem = barItem;
}

- 9,067
- 4
- 40
- 42
Jeremytripp 's working Code in Swift
I couldn't find the same code in Swift so I "translated" it myself:
func searchDisplayControllerWillBeginSearch(controller: UISearchDisplayController) {
self.searchDisplayController?.searchBar.showsCancelButton = true
var cancelButton: UIButton
var topView: UIView = self.searchDisplayController?.searchBar.subviews[0] as UIView
for subView in topView.subviews {
if subView.isKindOfClass(NSClassFromString("UINavigationButton")) {
cancelButton = subView as UIButton
cancelButton.setTitle("My Custom Title", forState: UIControlState.Normal)
}
}
}

- 8,303
- 3
- 41
- 54

- 609
- 7
- 16
If you just want to localized the default "Cancel" title for cancel button, I prefer to change the value of CFBundleDevelopmentRegion key from en to your localized region in Info.plist file in project.
Here is my change,
<key>CFBundleDevelopmentRegion</key>
<string>zh_CN</string>
after that, the default "Cancel" title will show as Chinese "取消". This change will also affect all the default region values, for example, the pasteboard operations' action titles on UITextField/UITextView will be localized, "Select" -> "选择", "Paste" -> "粘贴"...
By the way, the Info.plist file could be localized perfectly.
Enjoy!

- 5,777
- 2
- 37
- 69
Instead of referencing the non-public UINavigationButton class, I did the following. I'm hoping that it will make it through App Store review!
for (id subview in searchBar.subviews) {
if ([subview respondsToSelector:@selector(setTitle:)]) {
[subview setTitle:@"Map"];
}
}

- 5,999
- 1
- 38
- 54
If you're still having trouble with changing the Cancel button in iOS7, this is currently working for me:
-(void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller{
self.searchDisplayController.searchBar.showsCancelButton = YES;
UIButton *cancelButton;
UIView *topView = self.searchDisplayController.searchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(@"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
//Set the new title of the cancel button
[cancelButton setTitle:@"Hi" forState:UIControlStateNormal];
}
}

- 1,038
- 1
- 11
- 25
if the SearchBar is in the navigationBar, the code will be different than the usual answer; You need to search for NavigationBar's subviews instead.
-(void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller{
UINavigationBar * navigationBar = self.navigationController.navigationBar;
for (UIView *subView in navigationBar.subviews){
if([subView isKindOfClass:NSClassFromString(@"UINavigationButton")]){
[(UIButton*)subView setTitle:@"İptal" forState:UIControlStateNormal];
}
}}
and This work in iOS7+ , if you still can't set the title you should learn view debugging - This is how I solved this problem of mine.
This brief tutorial outlines the key points of View-Debugging very well:
http://www.raywenderlich.com/98356/view-debugging-in-xcode-6

- 8,077
- 2
- 68
- 66
if #available(iOS 13.0, *) {
controller.searchBar.setValue("Done", forKey:"cancelButtonText")
} else {
controller.searchBar.setValue("Done", forKey:"_cancelButtonText")
}
♂️
Actually controller.searchBar.setValue("Done", forKey:"cancelButtonText")
works for all iOS versions

- 1,048
- 2
- 14
- 22
Working short code in Swift 2.1 (iOS7-9 tested)
@IBOutlet weak var searchBar: UISearchBar!
func enableSearchBarCancelButton(enable: Bool, title: String? = nil) {
searchBar?.showsCancelButton = enable
if enable {
if let _cancelButton = searchBar?.valueForKey("_cancelButton"),
let cancelButton = _cancelButton as? UIButton {
cancelButton.enabled = enable //comment out if you want this button disabled when keyboard is not visible
if title != nil {
cancelButton.setTitle(title, forState: UIControlState.Normal)
}
}
}
}

- 144
- 11