Стилизация кнопки отмены в UISearchBar

У меня есть 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

То, что вы хотите сделать, довольно сложно. Нет встроенного крючка, чтобы попасть на кнопку отмены.

Однако есть несколько вариантов, если вы готовы открыть капот.

Во-первых, UISearchBar - это UIView, а кнопка "Отмена" также представляет собой представление, которое добавляется в панель поиска как подвью, как и следовало ожидать.

Я немного экспериментировал и могу сказать, что, когда кнопка находится на экране, она имеет размер 48,30.

Итак, в viewWillAppear вы можете сделать что-то вроде этого:

  • Найдите изображение кнопки отмены в [searchBar subviews], ища номер с размером 48,30. (Кажется, только один - это может измениться...) Вы можете быть вдвойне осторожны и искать ту, которая находится примерно в правильном положении (отличается пейзажем и портретом).

  • Добавьте подзаголовок к кнопке отмены.

  • Подвью должен быть UIControl (чтобы вы могли установить enabled = NO, чтобы убедиться, что события касания доходят до кнопки фактической отмены)

  • Он должен иметь правильный цвет и закругленные углы; вам нужно будет вымыть размер по причинам, которые я еще не понял (55,30, похоже, работает)

  • Это будет работать, если searchBar.showsCancelButton всегда ДА; если вы хотите, чтобы он исчез, когда вы не редактировали строку поиска, вам нужно будет найти крючок для добавления наложения каждый раз, когда появится кнопка отмены.

  • Как вы можете видеть, это какое-то уродливое мастерство. Сделайте это широко открытыми глазами.

Ответ 2

Вы можете использовать UIAppearance, чтобы стилизовать кнопку отмены без повторения подпунктов UISearchBar, но заголовок UIButton в настоящее время не имеет каких-либо методов, аннотированных с помощью 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
}
}

Ответ 3

Измените заголовок кнопки "Отмена":

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

Быстрый эквивалент:

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

Ответ 4

В 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 manifestnce. Попробуйте UIBarButtonItem.

Ответ 5

Хотя это может быть не совсем актуально для исходного вопроса, решение по-прежнему применимо в более широком смысле, чтобы попытаться настроить кнопку "Отмена" в 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];

}

И в viewcontroller, который использует эту панель поиска, следующий фрагмент кода заботится о том, чтобы отображать кнопку отмены и настраивать ее название:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    MyUISearchBar *sBar = (MyUISearchBar *)searchBar;
    [sBar setShowsCancelButton:YES];
    [sBar setCloseButtonTitle:@"Done" forState:UIControlStateNormal];   
}

Как ни странно, мне не нужно было ничего делать, чтобы скрыть кнопку отмены, поскольку она скрыта по умолчанию при выходе из режима поиска.

Ответ 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]];

Ответ 7

Если вы хотите настроить кнопку отмены на UISearchBar, вы должны получить объект UIButton из вашего объекта UISearchBar. Пример ниже

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

Ответ 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:...];

Это позволит вам настроить кнопку "Отмена" столько, сколько захотите.

Ответ 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

Swift 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 из этого fooobar.com/questions/196858/...

Мне пришлось внести несколько корректировок для его ответа на работу в моих спецификациях, которые идут ниже. Пожалуйста, я прошу 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 amd и посмотрите, на чём находится индексный контроль. Таким же образом u может установить свойства UITextField:

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

Ответ 15

У меня есть много элементов UISearchBar во всем моем приложении, поэтому я написал эту категорию, чтобы добавить свойство, чтобы вы могли получить доступ к mySearchBar.cancelButton. (Если вы новичок в категориях, читайте больше о расширении объектов с помощью категорий здесь.)

Имейте в виду, что вы должны получать доступ только при нажатии кнопки "Отмена" , потому что 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]];