Below is the error i am getting in my app, which is working fine in ios6.
[__NSCFString frame]: unrecognized selector sent to instance 0xc075290
I am not getting what is wrong in it. But i guess something related to UINavigationController. Please guide for above.
Thanks in advance.
UPDATE: After enabling Zombies i get this error.
[_UINavigationBarBackIndicatorView frame]: message sent to deallocated instance 0xc0fb860
-(void)viewWillAppear:(BOOL)animated
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"isAcceptTerms"]) {
[adBannerView setDelegate:self];
[adBannerView setHidden:NO];
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"isBannerShown"]) //-ive logic is applied
{
[self.adBannerView setHidden:YES];
[self.adBannerView setDelegate:nil];
}
}
else
{
[adBannerView setDelegate:nil];
[adBannerView setHidden:YES];
}
[self.navigationController.navigationBar setHidden:NO];
NSMutableDictionary *dictTemp =[[sqlmessenger shared]fetchOrders];
int count=[[sqlmessenger shared] isuserdetails];
if (count>0)
{
[self updateCoordinate];
}
NSArray *arrContorl = [self.navigationController.navigationBar subviews];
for(UIButton *btnTemp in arrContorl)
{
if([btnTemp isKindOfClass:[UIButton class]])
{
[btnTemp removeFromSuperview];
}
}
UIImageView *imgHeader= [[UIImageView alloc]initWithFrame:CGRectMake(0,0,320,44)];
[imgHeader setBackgroundColor:[UIColor clearColor]];
[imgHeader setImage:[UIImage imageNamed:#"setting.png"]];
[self.navigationController.navigationBar addSubview:imgHeader];
if(lblHeader)
{
lblHeader=nil ;
}
lblHeader = [[UILabel alloc]initWithFrame:CGRectMake(60,5,230,30)];
[lblHeader setBackgroundColor:[UIColor clearColor]];
[lblHeader setTextAlignment:UITextAlignmentLeft];
[lblHeader setTextColor:[UIColor whiteColor]];
[lblHeader setFont:[UIFont boldSystemFontOfSize:18.0]];
if([dictTemp count]==0 && contentView.hidden == FALSE)
{
[lblHeader setText:#"Terms of Service (EULA)"];
}
else
{
[lblHeader setFrame:CGRectMake(110,5,200,30)];
[lblHeader setFont:[UIFont boldSystemFontOfSize:20.0]];
[lblHeader setText:#"Settings"];
}
[self.navigationController.navigationBar addSubview:lblHeader];
}
Obviously, you are trying to access the frame property of a NSString object, which is not permitted, since this object does not have this property.
Try adding more details. (Add the code that causes the crash, usually crash stack are not that helpful).
UPDATE:
Still not sure what's going on, you need to do the actual debug, plant the necessary breakpoints log your variables, see what values they have etc.
I can give you some things you can try:
1.Not sure why are you adding subviews to the navigation bar. You can instead use the navigationItem property of the UIViewController, and then leftBarButtonItem of UINavigationItem, like :
For left bar button item : (make sure you hide the back button first)
self.navigationController.navigationItem.hidesBackButton = YES;
self.navigationItem.leftBarButtonItem = yourLeftBarButtonItem;
And for the right one :
self.navigationItem.rightBarButtonItem = yourRightBarButtonItem;
2.You're allocating the view and the label each time that viewController is appearing. That's inefficient. Both memory and time-wise. Instead, you can allocate them once and change the alpha channels.
I am working on IOS 7 application.By default its appearing like Pic(1).But I need to change it as Pic(2).I googled and found few answers for the requirement,but it has not changed.Or else I need to hide.So that I can manage with background image.This is first image
I used below code to modify it.But didnt succeed.
In .h file
#property(nonatomic,strong) IBOutlet UISearchBar *findSearchBar;
In .m file
#synthesize findSearchBar;
- (void)viewDidLoad
{
[super viewDidLoad];
[self setSearchIconToFavicon];
}
- (void)setSearchIconToFavicon
{
// The text within a UISearchView is a UITextField that is a subview of that UISearchView.
UITextField *searchField;
for (UIView *subview in self.findSearchBar.subviews)
{
if ([subview isKindOfClass:[UITextField class]]) {
searchField = (UITextField *)subview;
break;
}
}
if (searchField)
{
UIView *searchIcon = searchField.leftView;
if ([searchIcon isKindOfClass:[UIImageView class]])
{
NSLog(#"aye");
}
searchField.rightView = nil;
searchField.leftView = nil;
searchField.leftViewMode = UITextFieldViewModeNever;
searchField.rightViewMode = UITextFieldViewModeAlways;
}
}
I am not getting how to make the center of the view's image to nil.Its really killing my time.Please help me.where I had gone wrong.
UITextField *txfSearchField = [looksearchbar valueForKey:#"_searchField"];
[txfSearchField setBackgroundColor:[UIColor whiteColor]];
[txfSearchField setLeftViewMode:UITextFieldViewModeNever];
[txfSearchField setRightViewMode:UITextFieldViewModeNever];
[txfSearchField setBackground:[UIImage imageNamed:#"searchbar_bgImg.png"]];
[txfSearchField setBorderStyle:UITextBorderStyleNone];
//txfSearchField.layer.borderWidth = 8.0f;
//txfSearchField.layer.cornerRadius = 10.0f;
txfSearchField.layer.borderColor = [UIColor clearColor].CGColor;
txfSearchField.clearButtonMode=UITextFieldViewModeNever;
Try this may be it will help u........
I'm not sure how to left-align the placeholder, but as of iOS 5.0 there's a simple, supported way to modify the search bar's text field properties, e.g.:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setLeftViewMode:UITextFieldViewModeNever];
which will hide the magnifying glass icon.
You could try:
searchBar.setImage(UIImage(named: "yourimage")!, forSearchBarIcon: UISearchBarIcon.Clear, state: UIControlState.Normal)
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.
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"];
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!
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];
}
}
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.
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];
}
}
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].
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];
}
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;
}
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)
}
}
}
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!
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"];
}
}
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];
}
}
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
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
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)
}
}
}
}
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
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
}
}
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.
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)
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.
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:
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).
Add a subview to the cancel button.
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)
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)
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.
As you can see, this is some ugly tinkering. Do it with eyes wide open.
You can find the cancel button by looping through the subviews of the search bar and checking for the class type (instead of the size):
UIButton *cancelButton = nil;
for(UIView *subView in yourSearchBar.subviews){
if([subView isKindOfClass:UIButton.class]){
cancelButton = (UIButton*)subView;
}
}
And then change the tint color:
[cancelButton setTintColor:[UIColor colorWithRed:145.0/255.0 green:159.0/255.0 blue:179.0/255.0 alpha:1.0]];
If you want to configure your cancel button on UISearchBar you should get the UIButton object from your UISearchBar object. Example below
UISearchBar *s_bar = [[UISearchBar alloc] initWithFrame:CGRectMake(50,20,300,30)];
s_bar.delegate = self;
s_bar.barStyle = UIBarStyleDefault;
s_bar.showsCancelButton = YES;
UIButton *cancelButton;
for (id button in s_bar.subviews)
{
if ([button isKindOfClass:[UIButton class]])
{
cancelButton=(UIButton*)button;
break;
}
}
Custom UISearchBar and override method -addSubview:
- (void) addSubview:(UIView *)view {
[super addSubview:view];
if ([view isKindOfClass:UIButton.class]) {
UIButton *cancelButton = (UIButton *)view;
[cancelButton setBackgroundImage:[UIImage imageNamed:#"xxxx.png"] forState:UIControlStateNormal];
[cancelButton setBackgroundImage:[UIImage imageNamed:#"yyyy.png"] forState:UIControlStateHighlighted];
}
}
I'll give a detailed answered regarding the UIAppearance technique. First, you need to understand that the cancel button is a private UINavigationButton:UIButton. 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:...];
// only for a backButton in an UINavigationBar, not for a cancelButton in an UISearchBar
//[barButtonItemAppearanceInBar setBackButtonBackgroundImage:... forState:... barMetrics:...];
//[barButtonItemAppearanceInBar setBackButtonTitlePositionAdjustment:... forBarMetrics:...];
//[barButtonItemAppearanceInBar setBackButtonBackgroundVerticalPositionAdjustment:... forBarMetrics:...];
UIButton<UIAppearance> *buttonAppearanceInBar = [UIButton appearanceWhenContainedIn:barClass, nil];
// warning: doesn't work for iOS7+
[buttonAppearanceInBar setTitle:... forState:...];
This will let you customize your Cancel button as much as you want.
After you've initialized your UISearchBar, you can probe into it's subviews and customize each of them. Example:
for (UIView *view in searchBar.subviews) {
//if subview is the button
if ([[view.class description] isEqualToString:#"UINavigationButton"]) {
//change the button images and text for different states
[((UIButton *)view) setEnabled:YES];
[((UIButton *)view) setTitle:nil forState:UIControlStateNormal];
[((UIButton *)view) setImage:[UIImage imageNamed:#"button image"] forState:UIControlStateNormal];
[((UIButton *)view) setBackgroundImage:[UIImage imageNamed:#"button"] forState:UIControlStateNormal];
[((UIButton *)view) setBackgroundImage:[UIImage imageNamed:#"button_pressed"] forState:UIControlStateSelected];
[((UIButton *)view) setBackgroundImage:[UIImage imageNamed:#"button_pressed"] forState:UIControlStateHighlighted];
//if the subview is the background
}else if([[view.class description] isEqualToString:#"UISearchBarBackground"]) {
//put a custom gradient overtop the background
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[some uicolor] CGColor], (id)[[another uicolor] CGColor], nil];
[view.layer insertSublayer:gradient atIndex:0];
//if the subview is the textfield
}else if([[view.class description] isEqualToString:#"UISearchBarTextField"]){
//change the text field if you wish
}
}
Worked out great for me! Especially the gradient :)
Swift 2.1.1:
There's no simple way to hook in and style the search bar, you need to grab the subview manually from the search bar and then apply your changes.
var cancelButton: UIButton
let topView: UIView = self.customSearchController.customSearchBar.subviews[0] as UIView
for subView in topView.subviews {
if subView.isKindOfClass(NSClassFromString("UINavigationButton")!) {
cancelButton = subView as! UIButton
cancelButton.enabled = true
cancelButton.setTitle("TestTitle", forState: UIControlState.Normal) // Change to set the title
cancelButton.setBackgroundImage(UIImage(named: "ImageName"), forState: .Normal) // Change this to set a custom cancel button image, set the title to "" to remove 'Cancel' text
}
}
First of all I'd like to thank #Eliott from this https://stackoverflow.com/a/37381821/1473144
I had to make a few adjustments for his answer to work in my specs that go below.
Please, I ask the OP to update the accepted answer as it's VERY outdated.
Swift 3, iOS 10 & Xcode 8.2.1
searchBar.showsCancelButton = true
var cancelButton: UIButton
let topView: UIView = self.searchBar.subviews[0] as UIView
for subView in topView.subviews {
if let pvtClass = NSClassFromString("UINavigationButton") {
if subView.isKind(of: pvtClass) {
cancelButton = subView as! UIButton
cancelButton.setTitle("", for: .normal)
cancelButton.tintColor = UIColor.black
cancelButton.setImage(#imageLiteral(resourceName: "searchX"), for: .normal)
}
}
}
Well, here is function, which can change Cancel's button label. Modify it, if you want.
Usage is:
nStaticReplaceStringInView(mySearchBar, #"Cancel", #"NewCancelButtonLabel");
void nStaticReplaceStringInView(UIView * view, NSString * haystack, NSString * needle)
{
for(int i=0; i<[view.subviews count]; i++)
{
nStaticReplaceStringInView([view.subviews objectAtIndex:i], haystack,needle);
}
if([view respondsToSelector:#selector(titleForState:)])
{
//NSLog(#"%# || %#",[view titleForState:UIControlStateNormal], haystack);
if(NSStrEq([view titleForState:UIControlStateNormal] , haystack))
{
[view setTitle: needle forState: UIControlStateNormal];
}
}
}
- (void) searchBarTextDidBeginEditing:(UISearchBar *)theSearchBar
{
NSArray *arr = [theSearchBar subviews];
UIButton *cancelButton = [arr objectAtIndex:3];
[cancelButton setTitle:#"yourtitle" forState:UIControlStateNormal];
}
Just take a log of arr amd see at which index control lies. In the same way u can set UITextField properties:
NSArray *arr = [searchbar subviews];
UITextField *searchfield = [arr objectAtIndex:2];
[searchfield setTextAlignment:UITextAlignmentRight];
I have many UISearchBar items throughout my app, so I wrote this category to add a property so you can access mySearchBar.cancelButton. (If you're new to categories, read more about extending objects with Categories here.)
Keep in mind you should only access this when the Cancel button is visible because UISearchBar seems to create a new button object every time it shows. Don't save the pointer to the cancelButton, just get it when needed:
#interface UISearchBar (cancelButton)
#property (readonly) UIButton* cancelButton;
- (UIButton *) cancelButton;
#end
#implementation UISearchBar (cancelButton)
- (UIButton *) cancelButton {
for (UIView *subView in self.subviews) {
//Find the button
if([subView isKindOfClass:[UIButton class]])
{
return (UIButton *)subView;
}
}
NSLog(#"Error: no cancel button found on %#", self);
return nil;
}
#end
stupid way
for(id cc in [SearchBar subviews])
{
if([cc isKindOfClass:[UIButton class]])
{
UIButton *btn = (UIButton *)cc;
......
Do whatever you want
.......
}
}
extension UISearchBar {
var cancelButton : UIButton? {
let topView: UIView = self.subviews[0] as UIView
if let pvtClass = NSClassFromString("UINavigationButton") {
for v in topView.subviews {
if v.isKind(of: pvtClass) {
return v as? UIButton
}
}
}
return nil
}
}
UISearchBar *searchBar;
[searchBar setShowsCancelButton:YES animated:YES];
UIButton *cancelButton =
YES == [searchBar respondsToSelector:NSSelectorFromString(#"cancelButton")] ?
[searchBar valueForKeyPath:#"_cancelButton"] : nil;
cancelButton.titleEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 10);
[cancelButton setTitle:#"New :)" forState:UIControlStateNormal];
For iOS 11 and Swift 4.
Create a subclass of UISearchController.
Override method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("layout")
if let btn = searchBar.subviews[0].subviews[2] as? UIButton {
btn.frame = CGRect(x: 306, y: 20, width: 53, height: 30)
}
}
For iOS 10 & above, use following method
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTintColor:[UIColor blackColor]];
The best way to style the cancelButton is without using UIAppearance is like this it is for Swift5 iOS13 and it works best with UISearchResultController.searchBar too
extension UISearchBar {
func changeSearchBarAppearance(appearance: MyAppearance) {
self.barTintColor = appearance.searchbar.barTintColor
self.tintColor = appearance.searchbar.tintColor
if let textField = self.subviews.first?.subviews.last?.subviews.first {
textField.tintColor = .black
}
}
}
setting serachBar tintColor will set the tintColor of all items including the cancelButton but with this the blinker in the searchField will also be set with the same tintColor so find the textfield and set its tintColor will solve the blinker issue