web-dev-qa-db-fra.com

Comment activer le bouton d'annulation avec UISearchBar?

Dans l'application Contacts de l'iPhone, si vous saisissez un terme de recherche, appuyez sur le bouton "Rechercher", le clavier est masqué, MAIS le bouton d'annulation est toujours activé. Dans mon application, le bouton d'annulation est désactivé lorsque j'appelle resignFirstResponder.

Quelqu'un sait-il comment masquer le clavier tout en maintenant le bouton d'annulation activé?

J'utilise le code suivant:

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    [searchBar resignFirstResponder];
}

Le clavier disparaît, mais le bouton "Annuler" situé à droite du champ de recherche est désactivé. Par conséquent, je ne peux pas annuler la recherche. L'application Contacts maintient le bouton d'annulation à l'état activé.

Je pense qu'une solution consiste peut-être à plonger dans l'objet searchBar et à appeler resignFirstResponder dans le champ de texte lui-même, plutôt que dans la barre de recherche elle-même.

Toute entrée appréciée.

49
Christopher

essaye ça

for(id subview in [yourSearchBar subviews])
{
    if ([subview isKindOfClass:[UIButton class]]) {
        [subview setEnabled:YES];
    }
}
21
Malek_Jundi

Cette méthode a fonctionné sous iOS7. 

- (void)enableCancelButton:(UISearchBar *)searchBar
{
    for (UIView *view in searchBar.subviews)
    {
        for (id subview in view.subviews)
        {
            if ( [subview isKindOfClass:[UIButton class]] )
            {
                [subview setEnabled:YES];
                NSLog(@"enableCancelButton");
                return;
            }
        }
    }
}

(Veillez également à l'appeler n'importe où après l'utilisation de [_searchBar resignFirstResponder].)

29
David Douglas

La solution acceptée ne fonctionnera pas lorsque vous commencerez à faire défiler le tableau au lieu d'appuyer sur le bouton "Rechercher". Dans ce cas, le bouton "Annuler" sera désactivé.

C'est ma solution qui réactive le bouton "Annuler" chaque fois qu'il est désactivé à l'aide de KVO.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Search for Cancel button in searchbar, enable it and add key-value observer.
    for (id subview in [self.searchBar subviews]) {
        if ([subview isKindOfClass:[UIButton class]]) {
            [subview setEnabled:YES];
            [subview addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew context:nil];
        }
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    // Remove observer for the Cancel button in searchBar.
    for (id subview in [self.searchBar subviews]) {
        if ([subview isKindOfClass:[UIButton class]])
            [subview removeObserver:self forKeyPath:@"enabled"];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // Re-enable the Cancel button in searchBar.
    if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:@"enabled"]) {
        UIButton *button = object;
        if (!button.enabled)
            button.enabled = YES;
    }
}
10
Marián Černý

Depuis iOS 6, le bouton semble être un UINavigationButton (classe privée) au lieu d'un UIButton. 

J'ai modifié l'exemple ci-dessus pour ressembler à ceci.

for (UIView *v in searchBar.subviews) {
    if ([v isKindOfClass:[UIControl class]]) {
        ((UIControl *)v).enabled = YES;
    }
}

Cependant, ceci est évidemment fragile, puisque nous nous disputons avec les internes. Il peut également activer plus que le bouton, mais cela fonctionne pour moi jusqu'à ce qu'une meilleure solution soit trouvée. 

Nous devrions demander à Apple d'exposer cela. 

9
Ben L.

Cela semblait fonctionner pour moi (dans viewDidLoad):

__unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];

Je me rends compte que je devrais probablement utiliser UISearchDisplayController correctement, mais cette solution a été facile à mettre en œuvre.

8
n8chur

Vous pouvez utiliser l'API d'exécution pour accéder au bouton d'annulation.

UIButton *btnCancel = [self.searchBar valueForKey:@"_cancelButton"];
[btnCancel setEnabled:YES];
7

J'ai développé ce que d'autres ont déjà posté en implémentant cela comme une catégorie simple sur UISearchBar.

UISearchBar + alwaysEnableCancelButton.h

#import <UIKit/UIKit.h>

@interface UISearchBar (alwaysEnableCancelButton)

@end

UISearchBar + alwaysEnableCancelButton.m

#import "UISearchBar+alwaysEnableCancelButton.h"

@implementation UISearchBar (alwaysEnableCancelButton)

- (BOOL)resignFirstResponder
{
    for (UIView *v in self.subviews) {
        // Force the cancel button to stay enabled
        if ([v isKindOfClass:[UIControl class]]) {
            ((UIControl *)v).enabled = YES;
        }

        // Dismiss the keyboard
        if ([v isKindOfClass:[UITextField class]]) {
            [(UITextField *)v resignFirstResponder];
        }
    }

    return YES;
}
@end
5
Brains

Voici une solution légèrement plus robuste qui fonctionne sur iOS 7. Elle parcourt de manière récursive toutes les sous-vues de la barre de recherche pour s'assurer qu'elle active toutes les UIControls (qui inclut le bouton Annuler).

- (void)enableControlsInView:(UIView *)view
{
    for (id subview in view.subviews) {
        if ([subview isKindOfClass:[UIControl class]]) {
            [subview setEnabled:YES];
        }
        [self enableControlsInView:subview];
    }
}

Appelez cette méthode immédiatement après avoir appelé [self.searchBar resignFirstResponder] comme ceci:

[self enableControlsInView:self.searchBar];

Voila! Le bouton Annuler reste activé.

4
smileyborg
for (UIView *firstView in searchBar.subviews) {
    for(UIView* view in firstView.subviews) {
        if([view isKindOfClass:[UIButton class]]) {
             UIButton* button = (UIButton*) view;
             [button setEnabled:YES];
        }
    }
}
3
Abdul Rehman

J'ai trouvé une approche différente pour le faire fonctionner dans iOS 7.

Ce que j'essaie, c'est quelque chose comme l'application Twitter iOS iOS. Si vous cliquez sur la loupe dans l’onglet Timelines, la UISearchBar apparaît avec le bouton Annuler activé, le clavier affiché et l’écran des recherches récentes. Faites défiler l'écran des recherches récentes pour masquer le clavier mais garder le bouton Annuler activé.

Ceci est mon code de travail:

UIView *searchBarSubview = self.searchBar.subviews[0];
NSArray *subviewCache = [searchBarSubview valueForKeyPath:@"subviewCache"];
if ([subviewCache[2] respondsToSelector:@selector(setEnabled:)]) {
    [subviewCache[2] setValue:@YES forKeyPath:@"enabled"];
}

Je suis arrivé à cette solution en définissant un point d'arrêt sur le scrollViewWillBeginDragging: de ma vue de table. J'ai regardé dans ma UISearchBar et mis à nu ses sous-vues. Il y en a toujours un seul, de type UIView (ma variable searchBarSubview).

enter image description here

Ensuite, cette UIView contient une NSArray appelée subviewCache et j'ai remarqué que le dernier élément, qui est le troisième, est de type UINavigationButton et non dans l'API publique. Je me suis donc mis à utiliser plutôt le codage clé-valeur. J'ai vérifié si la UINavigationButton répondait à setEnabled: et, heureusement, c'est le cas. J'ai donc défini la propriété sur @YES. Il s'avère que cette UINavigationButton est le bouton Annuler.

Cela va forcément casser si Apple décide de modifier l’implémentation des entrailles de UISearchBar, mais c’est quoi. Cela fonctionne pour le moment.

2
Matthew Quiros

La plupart des solutions publiées ne sont pas robustes et laisseront le bouton Annuler désactivé dans diverses circonstances.

J'ai tenté d'implémenter une solution qui garde toujours le bouton Annuler activé, même lorsque vous effectuez des tâches plus complexes avec la barre de recherche. Ceci est implémenté en tant que sous-classe UISearchView personnalisée dans Swift 4. Il utilise la valeur (astuce forKey :) pour trouver à la fois le bouton annuler et le champ de recherche, et attend le moment où le champ de recherche se termine et réactive le bouton annuler. Il active également le bouton d'annulation lors du changement de l'indicateur showsCancelButton.

Il contient quelques affirmations pour vous avertir si les détails internes de UISearchBar changent un jour et l'empêcher de fonctionner.

import UIKit

final class CancelSearchBar: UISearchBar {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        guard let searchField = value(forKey: "_searchField") as? UIControl else {
            assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
            return
        }

        searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd)
    }

    override var showsCancelButton: Bool {
        didSet { enableSearchButton() }
    }

    @objc private func enableSearchButton() {
        guard showsCancelButton else { return }
        guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else {
            assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
            return
        }

        cancelButton.isEnabled = true
    }
}
2
Dag Ågren

Version Swift pour David Douglas answer (testée sur iOS9) 

func enableSearchCancelButton(searchBar: UISearchBar){
    for view in searchBar.subviews {
        for subview in view.subviews {
            if let button = subview as? UIButton {
                button.enabled = true
            }
        }
    }
}
2
Tal Haham

Pour iOS 10, Swift 3:

for subView in self.movieSearchBar.subviews {
    for view in subView.subviews {
        if view.isKind(of:NSClassFromString("UIButton")!) {
            let cancelButton = view as! UIButton
            cancelButton.isEnabled = true
        }
    }
}
1
Saoud Rizwan

En vous appuyant sur la réponse de smileyborg , il suffit de la placer dans votre recherche

- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{   
    dispatch_async(dispatch_get_main_queue(), ^{
        __block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *);
        void (^ensureCancelButtonRemainsEnabled)(UIView *);
        weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) {
            for (UIView *subview in view.subviews) {
                if ([subview isKindOfClass:[UIControl class]]) {
                [(UIControl *)subview setEnabled:YES];
                }
                weakEnsureCancelButtonRemainsEnabled(subview);
            }
        };

        ensureCancelButtonRemainsEnabled(searchBar);
    });
 }

Cette solution fonctionne bien sur iOS 7 et supérieur.

1
followben

Pour iOS 9/10 (testé), Swift 3 (plus court):

searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
0
neegra

Meilleure et facile méthode:

[(UIButton *)[self.searchBar valueForKey:@"_cancelButton"] setEnabled:YES];
0
Lal Krishna

Une meilleure solution est

[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
0
migrant

Vous pouvez créer votre CustomSearchBar héritant de UISearchBar et implémenter cette méthode:

- (void)layoutSubviews {

    [super layoutSubviews];

    @try {
        UIView *baseView = self.subviews[0];

        for (UIView *possibleButton in baseView.subviews)
        {
            if ([possibleButton respondsToSelector:@selector(setEnabled:)]) {
                [(UIControl *)possibleButton setEnabled:YES];
            }
        }
    }
    @catch (NSException *exception) {
        NSLog(@"ERROR%@",exception);
    }
}
0
Diego Lima