简体   繁体   English

在 UISearchBar 中设置取消按钮的样式

[英]Styling the cancel button in a UISearchBar

I have a UISearchBar that has a cancel button (it's displayed using -(void)setShowsCancelButton:animated ).我有一个 UISearchBar 有一个取消按钮(它使用-(void)setShowsCancelButton:animated )。 I've changed the tintColor of the search bar like this in an attempt to get a grayish searchbar:我已经像这样更改了搜索栏的tintColor以尝试获得灰色搜索栏:

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这就是现在的样子 - 注意取消按钮也是灰色的: 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有没有办法单独设置取消按钮的颜色,使其看起来更像这样: 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 .您可以使用UIAppearance设置取消按钮的样式,而无需迭代UISearchBar子视图,但UIButton标头当前没有任何用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.但这通常返回 nil 直到searchBar.setShowsCancelButton(true, animated: true)被调用。

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
}
}

Change the title of 'Cancel' button:更改“取消”按钮的标题:

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

Swift equivalent: Swift 等效项:

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

In iOS 5.0+, you can use the appearnce proxy.在 iOS 5.0+ 中,您可以使用appearnce代理。

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 (eg clear button).如果你使用[UIButton appearanceWhenContainedIn:[UISearchBar class], nil] ,它会影响其他按钮(例如清除按钮)。 So, you'd better not use UIButton 's appearnce.所以,你最好不要使用UIButton的外观。 Try UIBarButtonItem .试试UIBarButtonItem

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.尽管这可能与原始问题不完全相关,但该解决方案在尝试自定义 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):尽管如此,这就是我所做的(caelavel 和 Arenim 解决方案的组合):

Subclassed UISearchBar as MyUISearchBar with these two methods:使用以下两种方法将 UISearchBar 子类化为 MyUISearchBar:

-(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.首先,UISearchBar 是一个 UIView,而 Cancel 按钮也是一个视图,正如您所期望的那样,它作为子视图添加到搜索栏中。

I have experimented a little, and can tell you that when the button is onscreen it has a size of 48,30.我进行了一些实验,可以告诉您,当按钮在屏幕上时,它的大小为 48,30。

So in viewWillAppear, you can do something like this:因此,在 viewWillAppear 中,您可以执行以下操作:

  1. Find the cancel button view in [searchBar subviews] by looking for one with size 48,30.在 [searchBar subviews] 中查找大小为 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)子视图应该是一个 UIControl(这样你就可以设置 enabled = NO,以确保触摸事件到达实际的取消按钮)

  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)由于我还不明白的原因,您需要捏造大小(55,30 似乎有效)

  5. This will work if searchBar.showsCancelButton is always YES;如果 searchBar.showsCancelButton 始终为 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.睁大眼睛做这件事。

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.如果你想在UISearchBar上配置你的取消按钮,你应该从你的UISearchBar对象中获取UIButton对象。 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;
    }
}

I'll give a detailed answered regarding the UIAppearance technique.我将详细回答有关 UIAppearance 技术的问题。 First, you need to understand that the cancel button is a private UINavigationButton:UIButton.首先,您需要了解取消按钮是一个私有的 UINavigationButton:UIButton。 After some inspection, it appears that UINavigationButton will respond to those UIAppearance selectors:经过一些检查,似乎 UINavigationButton 会响应那些 UIAppearance 选择器:

// 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.巧合的是,这些选择器与 UIBarButtonItem 的选择器相匹配。 Meaning the trick is to use two separate UIAppearance to handle the private class UINavigationButton.这意味着技巧是使用两个单独的 UIAppearance 来处理私有类 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.这将使您可以根据需要自定义“取消”按钮。

Custom UISearchBar and override method -addSubview:自定义 UISearchBar 和覆盖方法 -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];
    }
}

Swift 2.1.1:斯威夫特 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首先,我要感谢来自这个https://stackoverflow.com/a/37381821/1473144 的@Eliott

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.请,我要求 OP 更新已接受的答案,因为它已经过时了。

Swift 3, iOS 10 & Xcode 8.2.1 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)
        }
    }

}

After you've initialized your UISearchBar, you can probe into it's subviews and customize each of them.初始化 UISearchBar 后,您可以探索它的子视图并自定义每个子视图。 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 :)尤其是渐变:)

- (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.只需记录 arr 和查看索引控制所在的日志。 In the same way u can set UITextField properties:以同样的方式你可以设置UITextField属性:

    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 .我的应用程序中有许多 UISearchBar 项目,所以我编写了这个类别来添加一个属性,以便您可以访问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.请记住,您应该只在“取消”按钮可见时访问它,因为 UISearchBar 似乎每次显示时都会创建一个新的按钮对象。 Don't save the pointer to the cancelButton, just get it when needed:不要保存指向 cancelButton 的指针,只在需要时获取它:

@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
        .......        
    }
}

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];
  }
 }
}
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.对于 iOS 11 和 Swift 4。创建 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)
        }
}

对于 iOS 10 及更高版本,请使用以下方法

[[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设置cancelButton按钮样式的最佳方法是不使用UIAppearance就像这样,它适用于Swift5 iOS13 ,它也适用于UISearchResultController.searchBar

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设置serachBar tintColor将设置tintColor所有项目,包括的cancelButton但此在的闪光灯searchField也将使用相同的设置tintColor所以找到textfield ,并设置其tintColor将解决这个问题信号灯

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM