web-dev-qa-db-fra.com

Styliser le bouton d'annulation dans une barre UISearchBar

J'ai un UISearchBar qui a un bouton d'annulation (il est affiché à l'aide de -(void)setShowsCancelButton:animated). J'ai changé la tintColor de la barre de recherche comme ceci pour tenter d'obtenir une barre de recherche grisâtre:

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

Voici à quoi cela ressemble maintenant - remarquez comme le bouton Annuler est également gris: http://twitpic.com/c0hte

Existe-t-il un moyen de définir la couleur du bouton d'annulation séparément afin qu'il ressemble davantage à ceci: http://twitpic.com/c0i6q

26
Chu Yeow

Ce que vous voulez faire est assez difficile. Il n'y a pas de crochet intégré pour accéder au bouton d'annulation.

Cependant, il y a plusieurs options si vous êtes prêt à ouvrir le capot.

Tout d'abord, UISearchBar est un UIView et le bouton Annuler est également une vue, qui est ajoutée à la barre de recherche en tant que sous-vue, comme vous le souhaitiez.

J'ai un peu expérimenté et je peux vous dire que lorsque le bouton est à l'écran, sa taille est de 48,30.

Donc, dans viewWillAppear, vous pouvez faire quelque chose comme ceci:

  1. Recherchez la vue du bouton d’annulation dans [searchBar subviews] en en recherchant une de taille 48,30. (Il semble qu’il n’en existe qu’un seul - cela pourrait changer ...) Vous pouvez faire preuve de la plus grande prudence et en rechercher un qui se trouve approximativement dans la position correcte (diffère entre paysage et portrait).

  2. Ajouter une sous-vue au bouton d'annulation. 

  3. La sous-vue doit être un UIControl (pour que vous puissiez définir enabled = NO, afin de vous assurer que les événements tactiles atteignent le bouton d'annulation réel).

  4. Il doit avoir la bonne couleur et des angles arrondis. vous devrez modifier la taille pour des raisons que je ne comprends pas encore (55,30 semble fonctionner)

  5. Cela fonctionnera si searchBar.showsCancelButton est toujours YES; si vous souhaitez qu'il disparaisse lorsque vous n'éditez pas la chaîne de recherche, vous devez rechercher un crochet pour ajouter la superposition à chaque fois que le bouton d'annulation apparaît.

  6. Comme vous pouvez le constater, il s’agit d’un vilain bricolage. Faites-le les yeux grands ouverts.

14
Amagrammer

Vous pouvez utiliser UIAppearance pour styler le bouton d'annulation sans itérer les sous-vues de la UISearchBar, mais l'en-tête UIButton n'a actuellement aucune méthode annotée avec UI_APPEARANCE_SELECTOR .

EDIT: explorez les vues secondaires jusqu'à obtenir le bouton d'annulation

Mais ceci retourne généralement nil jusqu'à ce que searchBar.setShowsCancelButton(true, animated: true) soit appelé.

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

Dans iOS 5.0+, vous pouvez utiliser le proxy appearnce.

Avant que la barre de recherche ne soit affichée:

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

Si vous utilisez [UIButton appearanceWhenContainedIn:[UISearchBar class], nil], cela affectera les autres boutons (par exemple, le bouton effacer). Donc, vous feriez mieux de ne pas utiliser l'apparence de UIButton. Essayez UIBarButtonItem.

20
Yiming Tang

Changer le titre du bouton 'Annuler':

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

équivalent Swift:

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

Bien que cela ne soit peut-être pas tout à fait pertinent par rapport à la question d'origine, la solution est toujours applicable dans le sens plus large du fait de personnaliser le bouton Annuler dans la barre UISearchBar. Je pense que cela aidera les autres qui sont coincés dans un tel scénario.

Ma situation était de changer le titre du bouton d'annulation, mais avec une torsion, dans laquelle je ne voulais pas afficher le bouton d'annulation par défaut, mais je voulais seulement qu'il apparaisse, lorsque l'utilisateur entre en mode de recherche (en cliquant dans le champ de recherche ). A cet instant, je voulais que le bouton d'annulation porte la légende "Terminé" ("Annuler" donnait un sens différent à mon écran, d'où la personnalisation). 

Néanmoins, voici ce que j'ai fait (une combinaison des solutions de Caelavel et d'Arenim):

Sous-classe UISearchBar en tant que MyUISearchBar avec ces deux méthodes:

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

}

Et dans le contrôleur de vue qui utilise cette barre de recherche, le code suivant prend en charge l'affichage du bouton d'annulation et la personnalisation de son titre:

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

Curieusement, je n'ai rien eu à faire pour cacher le bouton d'annulation, car il est masqué par défaut lorsque le mode de recherche est quitté.

17
Sara

Si vous souhaitez configurer votre bouton d'annulation sur UISearchBar, vous devez obtenir l'objet UIButton à partir de votre objet UISearchBar. Exemple ci-dessous

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

Vous pouvez trouver le bouton d'annulation en parcourant les sous-vues de la barre de recherche et en vérifiant le type de classe (au lieu de la taille):

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

Et puis changez la couleur de teinte:

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

Je vais vous donner une réponse détaillée concernant la technique UIAppearance. Tout d'abord, vous devez comprendre que le bouton d'annulation est un bouton privé UINavigationButton: UIButton. Après une inspection, il semble que UINavigationButton répondra à ces sélecteurs 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:)

Par coïncidence, ces sélecteurs correspondent à ceux d'un UIBarButtonItem. Cela signifie que l’astuce consiste à utiliser deux UIAppearance distinctes pour gérer la classe privée 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:...];

Cela vous permettra de personnaliser votre bouton Annuler autant que vous le souhaitez.

4
Cœur

UISearchBar personnalisé et méthode de substitution -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];
    }
}
4
iwill

Une fois que vous avez initialisé votre UISearchBar, vous pouvez sonder ses sous-vues et personnaliser chacune d’elles. Exemple:

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

    }

}

Travaillé très bien pour moi! Surtout le dégradé :)

Swift 2.1.1:

Il n'y a pas de moyen simple de lier et de styler la barre de recherche, vous devez saisir la sous-vue manuellement dans la barre de recherche, puis appliquer vos modifications.

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
   }
}
1
Elliott Davies

Tout d’abord, je voudrais remercier @Eliott pour cette https://stackoverflow.com/a/37381821/1473144

J'ai dû faire quelques ajustements pour que sa réponse fonctionne dans mes spécifications qui vont au-dessous de… .. S'il vous plaît, je demande à l'OP de mettre à jour la réponse acceptée car elle est TRÈS obsolète.

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

}
1
gaskbr

J'ai beaucoup d'articles UISearchBar dans mon application, alors j'ai écrit cette catégorie pour ajouter une propriété afin que vous puissiez accéder à mySearchBar.cancelButton. (Si vous êtes nouveau dans les catégories, en savoir plus sur l'extension d'objets avec les catégories ici .)

Gardez à l'esprit que vous devriez accéder à cela uniquement lorsque le bouton Annuler est visible car UISearchBar semble créer un nouvel objet bouton à chaque fois qu'il s'affiche. Ne sauvegardez pas le pointeur sur cancelButton, récupérez-le quand vous en avez besoin:

@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
0
Aaron Brager

Eh bien, voici la fonction, qui peut changer l’étiquette du bouton Annuler. Modifiez-le, si vous voulez. L'usage est:

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];
  }
 }
}
0
Arenim
- (void) searchBarTextDidBeginEditing:(UISearchBar *)theSearchBar 
{        
    NSArray *arr = [theSearchBar subviews];
    UIButton *cancelButton = [arr objectAtIndex:3];
    [cancelButton setTitle:@"yourtitle" forState:UIControlStateNormal];    
}

Il suffit de prendre un journal d’arrêt AMD pour voir à quel index se situe De la même manière, vous pouvez définir les propriétés UITextField:

    NSArray *arr = [searchbar subviews];
    UITextField *searchfield = [arr objectAtIndex:2];
    [searchfield setTextAlignment:UITextAlignmentRight];
0
Abhishek Singh
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];
0
Roman Solodyashkin

Pour iOS 11 et Swift 4 . Créez une sous-classe de UISearchController . Méthode de remplacement:

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)
        }
}
0
Mazorati
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
}
}
0
Abhishek Bedi

manière stupide 

for(id cc in [SearchBar subviews])
{
    if([cc isKindOfClass:[UIButton class]])
    {
        UIButton *btn = (UIButton *)cc;
        ......
        Do whatever you want
        .......        
    }
}
0
LucKy_one