Estilizando o botão de cancelamento em uma UISearchBar

Eu tenho um UISearchBar que tem um botão de cancelamento (é exibido usando -(void)setShowsCancelButton:animated ). Eu mudei o tintColor da barra de pesquisa assim em uma tentativa de obter uma barra de pesquisa acinzentada:

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

Isto é o que parece agora – observe como o botão de cancelamento também é cinza: http://twitpic.com/c0hte

Existe uma maneira de definir a cor do botão de cancelamento separadamente para que fique mais parecido com isto: http://twitpic.com/c0i6q

O que você quer fazer é bem difícil. Não há um gancho embutido para acessar o botão de cancelamento.

No entanto, existem algumas opções se você estiver disposto a abrir o capô.

Em primeiro lugar, o UISearchBar é um UIView, e o botão Cancelar também é uma visualização, que é adicionada à barra de pesquisa como uma subvisualização, exatamente como seria de se esperar.

Eu experimentei um pouco e posso dizer que quando o botão está na canvas, ele tem um tamanho de 48,30.

Então, no viewWillAppear, você pode fazer algo assim:

  1. Encontre a exibição do botão de cancelamento em [subviews do searchBar] procurando por um com tamanho 48,30. (Só parece haver um – isso pode mudar …) Você poderia ser duplamente cuidadoso e procurar um que esteja aproximadamente na posição correta (difere na paisagem e no retrato).

  2. Adicione uma subvisualização ao botão de cancelamento.

  3. A subvisualização deve ser um UIControl (para que você possa definir enabled = NO, para garantir que os events de toque cheguem ao botão de cancelamento real)

  4. Precisa ter a cor certa e cantos arredondados; você precisará falsificar o tamanho por razões que eu ainda não entendi (55,30 parece funcionar)

  5. Isso funcionará se searchBar.showsCancelButton for sempre YES; Se você quiser que ele desapareça quando não estiver editando a string de pesquisa, você precisará encontrar um gancho para adicionar a sobreposição toda vez que o botão Cancelar aparecer.

  6. Como você pode ver, este é um remendo feio. Faça com os olhos bem abertos.

Você pode usar UIAppearance para estilizar o botão de cancelamento sem iterar UIButton do UIButton , mas o header UIButton não possui atualmente nenhum método anotado com UI_APPEARANCE_SELECTOR .

EDIT: Drill down as subvisualizações até você obter esse botão de cancelamento

Mas isso geralmente retorna nulo até que searchBar.setShowsCancelButton(true, animated: true) seja chamado.

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

No iOS 5.0+, você pode usar o proxy de appearnce .

Antes da barra de pesquisa ser exibida:

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

Se você usar [UIButton appearanceWhenContainedIn:[UISearchBar class], nil] , isso afetará outros botões (por exemplo, botão limpar). Então, é melhor você não usar a UIButton do UIButton. Tente UIBarButtonItem .

Altere o título do botão ‘Cancelar’:

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

Equivalente Swift:

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

Embora isso possa não ser exatamente relevante para a pergunta original, a solução ainda é aplicável no sentido mais amplo de tentar personalizar o botão Cancelar no UISearchBar. Pensei que isso ajudaria os outros que estão presos em tal cenário.

Minha situação era mudar o título do botão de cancelamento, mas com uma torção, onde eu não queria mostrar o botão de cancelamento por padrão, mas só queria que ele aparecesse, quando o usuário entra no modo de pesquisa (clicando no campo de texto de pesquisa ). Neste instante, eu queria que o botão cancelar carregasse a legenda “Concluído” (“Cancelar” estava dando um significado diferente à minha canvas, daí a personalização).

No entanto, eis o que fiz (uma combinação das soluções do caelavel e da Arenim):

UISearchBar Subclassd como MyUISearchBar com estes dois 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]; } 

E no viewcontroller que usa este Searchbar, o seguinte código cuida de mostrar o botão de cancelamento e personalizar seu título:

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

Estranhamente, não precisei fazer nada para ocultar o botão de cancelamento, pois ele fica oculto por padrão, quando o modo de pesquisa é encerrado.

Você pode encontrar o botão de cancelamento percorrendo as subvisualizações da barra de pesquisa e verificando o tipo de class (em vez do tamanho):

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

E depois mude a cor da tonalidade:

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

Se você quiser configurar seu botão de cancelamento no UISearchBar você deve obter o object UIButton do seu object UIButton . Exemplo abaixo

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

UISearchBar personalizado e replace o método -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]; } } 

Vou dar uma resposta detalhada sobre a técnica UIAppearance. Primeiro, você precisa entender que o botão de cancelamento é um UIBavigationButton privado: UIButton. Após alguma inspeção, parece que o UINavigationButton responderá a esses seletores de 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:) 

Coincidentemente, esses seletores correspondem aos de um UIBarButtonItem. O que significa o truque é usar dois UIAppearance separados para manipular a class privada UINavigationButton.

 /* dual appearance technique by Cœur to customize a UINavigationButton */ Class barClass = [UISearchBar self]; UIBarButtonItem *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 *buttonAppearanceInBar = [UIButton appearanceWhenContainedIn:barClass, nil]; // warning: doesn't work for iOS7+ [buttonAppearanceInBar setTitle:... forState:...]; 

Isso permitirá que você personalize seu botão Cancelar o quanto quiser.

Depois de inicializar sua UISearchBar, você pode investigar suas subvisualizações e personalizar cada uma delas. Exemplo:

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

Funcionou muito bem para mim! Especialmente o gradiente 🙂

Swift 2.1.1:

Não existe uma maneira simples de inserir e estilizar a barra de pesquisa, você precisa pegar a subvisualização manualmente a partir da barra de pesquisa e depois aplicar as alterações.

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

Primeiro de tudo, gostaria de agradecer @Eliott deste https://stackoverflow.com/a/37381821/1473144

Eu tive que fazer alguns ajustes para que sua resposta funcionasse em minhas especificações abaixo. Por favor, peço ao OP para atualizar a resposta aceita, pois está muito desatualizado.

Swift 3, iOS 10 e 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) } } } 

Bem, aqui está a function, que pode alterar o label do botão do Cancel. Modifique-o, se quiser. O uso é:

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

Basta ter um log de arrmd ver em qual controle de índice se encontra. Da mesma forma você pode definir propriedades UITextField :

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

Eu tenho muitos itens UISearchBar em todo o meu aplicativo, então eu escrevi esta categoria para adicionar uma propriedade para que você possa acessar mySearchBar.cancelButton . (Se você é novato em categorias, leia mais sobre como estender objects com Categorias aqui .)

Lembre-se de que você deve acessar isso quando o botão Cancelar estiver visível, pois o UISearchBar parece criar um novo object de botão toda vez que é exibido. Não salve o ponteiro para o cancelButton, apenas pegue quando necessário:

 @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 

maneira estúpida

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

Para iOS 11 e Swift 4. Crie uma subclass de UISearchController. Substituir método:

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