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

Posted

技术标签:

【中文标题】在 UISearchBar 中设置取消按钮的样式【英文标题】:Styling the cancel button in a UISearchBar 【发布时间】:2009-07-29 13:10:53 【问题描述】:

我有一个带有取消按钮的 UISearchBar(它使用-(void)setShowsCancelButton:animated 显示)。我已经像这样更改了搜索栏的tintColor,试图获得一个灰色的搜索栏:

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

这就是现在的样子 - 注意取消按钮也是灰色的:http://twitpic.com/c0hte

有没有办法单独设置取消按钮的颜色,让它看起来更像这样:http://twitpic.com/c0i6q

【问题讨论】:

为什么不在这里嵌入图片? 【参考方案1】:

您可以使用UIAppearance 来设置取消按钮的样式,而无需迭代UISearchBar 的子视图,但the UIButton header does not currently have any methods annotated with UI_APPEARANCE_SELECTOR

编辑:向下钻取子视图,直到获得取消按钮

但这通常返回 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


【讨论】:

[[UIButton appearanceWhenContainedIn:[MySearchBar class], nil] setBackgroundImage:... forState:UIControlStateNormal]; 有趣的是,取消按钮声明它是 UISearchBar 标题中的 UIButton,但我的 UIBarButton 外观代理覆盖了样式。所以我最终设计了我的取消按钮[[UIBarButtonItem appearanceWhenContainedIn: [UISearchBar class], [ContainingClass class], nil] setTintColor:UIColorFromRGB(0xc5c2b6)]; 这是 ios 5+ 的最佳答案。鸡的评论应该在自己的答案中。 请注意,使用 [[UIButton appearance... 将编辑用于擦除您键入的内容的 X 按钮。改用 [[UIBarButtonItem 外观。 @JasonZhao UIBarButtonItem 有不同的背景图片外观选择器,setBackgroundImage:forState:barMetrics:【参考方案2】:

在 iOS 5.0+ 中,您可以使用 appearnce 代理。

在显示搜索栏之前:

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];

如果您使用[UIButton appearanceWhenContainedIn:[UISearchBar class], nil],它会影响其他按钮(例如清除按钮)。所以,你最好不要使用UIButton 的外观。试试UIBarButtonItem

【讨论】:

这才是真正解决问题的方法!谢谢!【参考方案3】:

更改“取消”按钮的标题:

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

Swift 等效项:

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

【讨论】:

从 iOS5 开始,这是最简单的方法。谢谢 Neru。【参考方案4】:

尽管这可能与原始问题不完全相关,但在尝试自定义 UISearchBar 中的取消按钮的更大意义上,该解决方案仍然适用。认为这会帮助陷入这种情况的其他人。

我的情况是更改取消按钮的标题,但有一个转折,其中我不想默认显示取消按钮,而只想在用户进入搜索模式时显示它(通过单击内部搜索文本字段)。此时,我希望取消按钮带有“完成”的标题(“取消”给我的屏幕赋予了不同的含义,因此进行了自定义)。

不过,这就是我所做的(结合了 caelavel 和 Arenim 的解决方案):

使用这两种方法将 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];


在使用此搜索栏的视图控制器中,以下代码负责显示取消按钮并自定义其标题:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar

    MyUISearchBar *sBar = (MyUISearchBar *)searchBar;
    [sBar setShowsCancelButton:YES];
    [sBar setCloseButtonTitle:@"Done" forState:UIControlStateNormal];   

奇怪的是,我不需要做任何事情来隐藏取消按钮,因为默认情况下,当退出搜索模式时它是隐藏的。

【讨论】:

【参考方案5】:

你想做的事情很艰难。取消按钮没有内置的钩子。

但是,如果您愿意打开引擎盖,有几个选择。

首先,UISearchBar 是一个 UIView,Cancel 按钮也是一个视图,正如你所期望的那样,它作为子视图添加到搜索栏中。

我做了一点实验,可以告诉你,当按钮在屏幕上时,它的大小为 48,30。

所以在 viewWillAppear 中,你可以这样做:

    在 [searchBar 子视图] 中查找大小为 48,30 的取消按钮视图。 (似乎只有一个——这可能会改变...)您可以加倍小心,寻找一个位置大致正确的(横向和纵向不同)。

    向取消按钮添加子视图。

    子视图应该是一个 UIControl(这样您就可以设置 enabled = NO,以确保触摸事件到达实际的取消按钮)

    它需要有正确的颜色和圆角;由于我还不明白的原因,您需要捏造尺寸(55,30 似乎有效)

    如果 searchBar.showsCancelButton 始终为 YES,这将起作用;如果您希望它在不编辑搜索字符串时消失,则需要在每次出现取消按钮时找到一个挂钩来添加叠加层。

    如您所见,这是一些丑陋的修补。睁大眼睛做。

【讨论】:

我希望它不会变成这样 :) 它确实按照您描述的方式工作(除了取消按钮的大小对我来说是 55x30)。仍然需要尝试使外观正确,但除了这种方法的丑陋之外,它可能有效。非常感谢您的帮助! 嗯!我想做的就是更改 Scope Button 栏的 tintColor。也许类似的东西也会让我看到这种观点......但它是如此脆弱。耶奇。决策决策。 jandrea -- 提出你自己的问题并指出我的问题 -- 我将尝试解释如何做类似的事情。 @Dermot 然后使用 [subview isKindOfClass:[UIButton class]] 找到取消按钮 如果您的目标是 iOS 5+,请考虑小鸡和唐一鸣给出的答案(在此页面上搜索这些名称以找到它们)。【参考方案6】:

您可以通过遍历搜索栏的子视图并检查类类型(而不是大小)来找到取消按钮:

UIButton *cancelButton = nil;
for(UIView *subView in yourSearchBar.subviews)
    if([subView isKindOfClass:UIButton.class])
    cancelButton = (UIButton*)subView;
    

然后改变色调颜色:

[cancelButton setTintColor:[UIColor colorWithRed:145.0/255.0 green:159.0/255.0 blue:179.0/255.0 alpha:1.0]];

【讨论】:

这给出了一个警告,即 cancelButton 可能不会响应 setTintColor,因为这是一个未记录的 API。这也可能导致您的应用因使用未记录的 API 而被 Apple 拒绝。 不要忘记 if 语句中的中断 subViews 只包含 UISearchBarBackground 和 UISearchBarTextField 类型的视图,不包含 UIButton 类型的视图。我正在使用 iOS5。【参考方案7】:

如果您想在UISearchBar 上配置取消按钮,您应该从您的UISearchBar 对象中获取UIButton 对象。下面的例子

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;
    

【讨论】:

这行得通,但只有在取消按钮可见时才能这样做。 对我来说,如果永远不会返回 true。我在这里只得到 UISearchBarBackground UISearchBarTextField 类型的视图。这是我的代码。 for (UIView *subview in [searchField subviews]) NSLog([NSString stringWithFormat:@"%@", [subview class]); if([subview isKindOfClass: [UIButton class]]) //做点什么。 //end if //end for 但这 if 永远不会返回 true。这些 NSLog 打印 UISearchBarBackground 和 UISearchBarTextField ...【参考方案8】:

自定义 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];
    

【讨论】:

天才,我喜欢这种方法比凌乱的迭代要好得多。【参考方案9】:

我将给出关于 UIAppearance 技术的详细回答。首先,您需要了解取消按钮是一个私有的 UINavigationButton:UIButton。经过一番检查,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:)

巧合的是,这些选择器与 UIBarButtonItem 的选择器相匹配。这意味着诀窍是使用两个单独的 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:...];

这将使您可以根据需要自定义“取消”按钮。

【讨论】:

要在 iOS7+ 上更改标题,您应该为您的应用使用本地化(具有文件夹 de.lproj、fr.lproj、...)【参考方案10】:

初始化 UISearchBar 后,您可以探测它的子视图并自定义每个子视图。示例:

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

    


对我来说效果很好!尤其是渐变色:)

【讨论】:

【参考方案11】:

斯威夫特 2.1.1:

没有简单的方法来挂钩和设置搜索栏的样式,您需要从搜索栏中手动获取子视图,然后应用您的更改。

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
   

【讨论】:

【参考方案12】:

首先我要感谢@Eliott 来自https://***.com/a/37381821/1473144

我必须对他的回答进行一些调整才能在下面的规范中工作。 请,我要求 OP 更新已接受的答案,因为它已经非常过时了。

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)
        
    


【讨论】:

【参考方案13】:

嗯,这里是函数,它可以改变取消按钮的标签。修改它,如果你愿意。 用法是:

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];
  
 

【讨论】:

【参考方案14】:
- (void) searchBarTextDidBeginEditing:(UISearchBar *)theSearchBar 
        
    NSArray *arr = [theSearchBar subviews];
    UIButton *cancelButton = [arr objectAtIndex:3];
    [cancelButton setTitle:@"yourtitle" forState:UIControlStateNormal];    

只需记录 arr 并查看索引控制所在的位置。同样你可以设置UITextField属性:

    NSArray *arr = [searchbar subviews];
    UITextField *searchfield = [arr objectAtIndex:2];
    [searchfield setTextAlignment:UITextAlignmentRight];

【讨论】:

【参考方案15】:

我的应用程序中有很多 UISearchBar 项目,所以我编写了这个类别来添加一个属性,以便您可以访问mySearchBar.cancelButton。 (如果您不熟悉类别,read more about extending objects with Categories here。)

请记住,您应该仅在“取消”按钮可见时访问它,因为 UISearchBar 似乎每次显示时都会创建一个新按钮对象。不保存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

【讨论】:

【参考方案16】:

笨方法

for(id cc in [SearchBar subviews])

    if([cc isKindOfClass:[UIButton class]])
    
        UIButton *btn = (UIButton *)cc;
        ......
        Do whatever you want
        .......        
    

【讨论】:

【参考方案17】:
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


【讨论】:

【参考方案18】:
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];

【讨论】:

【参考方案19】:

适用于 iOS 11 和 Swift 4。 创建 UISearchController 的子类。 覆盖方法:

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)
        

【讨论】:

【参考方案20】:

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

[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTintColor:[UIColor blackColor]];

【讨论】:

【参考方案21】:

设置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
        
    

设置serachBar tintColor 将设置包括cancelButton 在内的所有项目的tintColor 但这样searchField 中的闪光灯也将设置为相同的tintColor 所以找到textfield并设置其tintColor 将解决闪光灯问题

【讨论】:

以上是关于在 UISearchBar 中设置取消按钮的样式的主要内容,如果未能解决你的问题,请参考以下文章

如果在 viewDidAppear 之前显示,UISearchBar 取消按钮没有样式

UISearchBar 取消按钮颜色?

在 EXTJS 中设置按钮样式

如何在android中设置按钮样式

在 UISearchDisplayController 中设置 UISearchBar 的边框颜色

在React native中设置按钮样式