web-dev-qa-db-fra.com

UISearchbar clearButton oblige le clavier à apparaître

J'ai un UISearchBar qui agit comme un filtre en direct pour une vue de table. Lorsque le clavier est désactivé via endEditing :, le texte de la requête et le bouton gris "transparent" demeurent. À partir de là, si j'appuie sur le bouton gris "clear", le clavier réapparaît lorsque le texte est effacé.

Comment puis-je empêcher cela? Si le clavier n'est pas ouvert, je souhaite que ce bouton efface le texte sans rouvrir le clavier.

Il existe une méthode de protocole qui est appelée lorsque je tape sur le bouton d'effacement. Toutefois, l'envoi d'un message resignFirstResponder à UISearchBar n'a aucun effet sur le clavier.

43
Jens Kohl

C'est une vieille question et je viens de rencontrer le même problème et j'ai réussi à le résoudre de la manière suivante:

Lorsque la méthode searchBar:textDidChange: de UISearchBarDelegate est appelée en raison du fait que l'utilisateur a cliqué sur le bouton 'effacer', le searchBar n'est pas encore devenu le premier répondeur. Nous pouvons donc en tirer parti pour détecter le moment précis où l'utilisateur voulait effacer la recherche et ne pas mettre en évidence le searchBar et/ou faire autre chose.

Pour garder une trace de cela, nous devons déclarer un ivar BOOL dans notre viewController qui est également le délégué searchBar (appelons-le shouldBeginEditing) et le définir avec une valeur initiale de YES (supposons que notre classe viewController s'appelle SearchViewController):

@interface SearchViewController : UIViewController <UISearchBarDelegate> {
    // all of our ivar declarations go here...
    BOOL shouldBeginEditing;
    ....
}

...
@end



@implementation SearchViewController
...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        ...
        shouldBeginEditing = YES;
    }
}
...
@end

Plus tard, dans le UISearchBarDelegate, nous implémentons les méthodes searchBar:textDidChange: et searchBarShouldBeginEditing::

- (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)searchText {
    NSLog(@"searchBar:textDidChange: isFirstResponder: %i", [self.searchBar isFirstResponder]);
    if(![searchBar isFirstResponder]) {
        // user tapped the 'clear' button
        shouldBeginEditing = NO;
        // do whatever I want to happen when the user clears the search...
    }
}


- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)bar {
    // reset the shouldBeginEditing BOOL ivar to YES, but first take its value and use it to return it from the method call
    BOOL boolToReturn = shouldBeginEditing;
    shouldBeginEditing = YES;
    return boolToReturn;
}

En gros, c'est ça.

Meilleur

120
boliva

J'ai constaté que resignFirstResponder ne fonctionnait pas lorsque textDidChange était appelé d'une touche sur le "bouton d'effacement". Cependant, utiliser performSelection: withObject: afterDelay: semble être une solution de contournement efficace:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if ([searchText length] == 0) {
        [self performSelector:@selector(hideKeyboardWithSearchBar:) withObject:searchBar afterDelay:0];
    }
}

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

J'ai trouvé un moyen assez sûr de savoir si le bouton Effacer a été enfoncé et d'ignorer les moments où l'utilisateur supprime simplement le dernier caractère de la barre UISearchBar. C'est ici :

- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    _isRemovingTextWithBackspace = ([searchBar.text stringByReplacingCharactersInRange:range withString:text].length == 0);

    return YES;
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if (searchText.length == 0 && !_isRemovingTextWithBackspace)
    {
        NSLog(@"Has clicked on clear !");
    }
}

Assez simple et simple, n'est-ce pas :)? La seule chose à noter est que si l'utilisateur clique sur le bouton d'effacement lors de la modification de l'UITextField de UISearchBar, vous aurez deux pings, alors que vous n'en obtiendrez qu'une si l'utilisateur clique dessus sans la modifier.


Edit: Je ne peux pas le tester, mais voici la version Swift, selon Rotem:

var isRemovingTextWithBackspace = false

func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool
{
    self.isRemovingTextWithBackspace = (NSString(string: searchBar.text!).stringByReplacingCharactersInRange(range, withString: text).characters.count == 0)
    return true
}

func searchBar(searchBar: UISearchBar, textDidChange searchText: String)
{
    if searchText.characters.count == 0 && !isRemovingTextWithBackspace
    { 
        NSLog("Has clicked on clear !")
    }
}

@ Mise à jour de Rotem (Swift2):

var isRemovingTextWithBackspace = false

func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    self.isRemovingTextWithBackspace = (NSString(string: searchBar.text!).stringByReplacingCharactersInRange(range, withString: text).characters.count == 0)
    return true
}

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
    if searchText.characters.count == 0 && !isRemovingTextWithBackspace {
        NSLog("Has clicked on clear!")
    }
}
9
CyberDandy

J'ai utilisé une combinaison de la réponse de @ boliva et de celle de @ radiospiel answer à une autre SO question:

@interface SearchViewController : UIViewController <UISearchBarDelegate> {
    // all of our ivar declarations go here...
    BOOL shouldBeginEditing;
    ....
}

...
@end

@implementation SearchViewController
...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        ...
        shouldBeginEditing = YES;
    }
}
...

- (void) searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
    // TODO - dynamically update the search results here, if we choose to do that.

    if (![searchBar isFirstResponder]) {
        // The user clicked the [X] button while the keyboard was hidden
        shouldBeginEditing = NO;
    }
    else if ([searchText length] == 0) {
        // The user clicked the [X] button or otherwise cleared the text.
        [theSearchBar performSelector: @selector(resignFirstResponder)
                        withObject: nil
                        afterDelay: 0.1];
    }
}

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)bar {
    // reset the shouldBeginEditing BOOL ivar to YES, but first take its value and use it to return it from the method call
    BOOL boolToReturn = shouldBeginEditing;
    shouldBeginEditing = YES;
    return boolToReturn;
}
@end
8
benvolioT

La meilleure solution de mon expérience est simplement de mettre une UIButton (avec un arrière-plan clair et aucun texte) au-dessus du bouton d'effacement du système et de connecter une IBAction

Avec l'autolayout, c'est plus que facile

- (IBAction)searchCancelButtonPressed:(id)sender {

    [self.searchBar resignFirstResponder];
    self.searchBar.text = @"";

    // some of my stuff
    self.model.fastSearchText = nil;
    [self.model fetchData];
    [self reloadTableViewAnimated:NO];

}
3
Peter Lapisu

En appuyant sur le bouton "effacer" dans une barre UISearchBar, le clavier s'ouvre automatiquement même si vous appelez searchBar.resignFirstResponder() dans la méthode textDidChange UISearchBarDelegate.

Pour masquer le clavier lorsque vous appuyez sur le «x» pour effacer le UISearchBar, utilisez le code suivant. Ainsi, même si vous appelez textDidChange lors de la saisie dans la barre UISearchBar, vous ne masquez le clavier que si vous supprimez tout le texte qu'il contient, que vous utilisiez le bouton Supprimer ou que vous cliquez sur le "x":

extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar.text!.count == 0 {
            DispatchQueue.main.async {
                searchBar.resignFirstResponder()
            }
        } else {
            // Code to make a request here.
        }
    }
}
1
luismgb

Pour ceux d'entre vous qui utilisent la UISearchController dans iOS 8 et versions ultérieures, vous voudrez simplement sous-classer la UISearchController. Par souci d’exhaustivité, vous pouvez également souhaiter masquer le bouton cancel, comme je l’avais fait, car effacer le texte de la variable UISearchBar est en réalité une annulation de la recherche. J'ai ajouté le code ci-dessous si vous souhaitez l'utiliser.

L'avantage de ceci est que vous pourrez l'utiliser pour n'importe quelle classe et vue, plutôt que d'exiger une sous-classe de UIViewController. Je vais même inclure comment j'initialise ma UISearchController au bas de cette solution.

Barre de recherche FJ

Cette classe ne doit être remplacée que si vous souhaitez masquer le bouton d'annulation comme je l'ai fait. Le marquage searchController.searchBar.showsCancelButton = NO ne semble pas fonctionner dans iOS 8. Je n'ai pas testé iOS 9.

FJSearchBar.h

Vide, mais placé ici pour être complet.

@import UIKit;

@interface FJSearchBar : UISearchBar

@end

FJSearchBar.m

#import "FJSearchBar.h"

@implementation FJSearchBar

- (void)setShowsCancelButton:(BOOL)showsCancelButton {
    // do nothing
}

- (void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
    // do nothing
}

@end

FJSearchController

Voici où vous voulez faire les vrais changements. Je divise la UISearchBarDelegate dans sa propre catégorie car, à mon humble avis, les catégories rendent les classes plus propres et plus faciles à gérer. Si vous souhaitez conserver le délégué dans l'interface/l'implémentation de la classe principale, n'hésitez pas.

FJSearchController.h

@import UIKit;

@interface FJSearchController : UISearchController

@end

@interface FJSearchController (UISearchBarDelegate) <UISearchBarDelegate>

@end

FJSearchController.m

#import "FJSearchController.h"
#import "FJSearchBar.h"

@implementation FJSearchController {
@private
    FJSearchBar *_searchBar;
    BOOL _clearedOutside;
}

- (UISearchBar *)searchBar {
    if (_searchBar == nil) {
        // if you're not hiding the cancel button, simply uncomment the line below and delete the FJSearchBar alloc/init
        // _searchBar = [[UISearchBar alloc] init];
        _searchBar = [[FJSearchBar alloc] init];
        _searchBar.delegate = self;
    }
    return _searchBar;
}

@end

@implementation FJSearchController (UISearchBarDelegate)

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    // if we cleared from outside then we should not allow any new editing
    BOOL shouldAllowEditing = !_clearedOutside;
    _clearedOutside = NO;
    return shouldAllowEditing;
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    // hide the keyboard since the user will no longer add any more input
    [searchBar resignFirstResponder];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (![searchBar isFirstResponder]) {
        // the user cleared the search while not in typing mode, so we should deactivate searching
        self.active = NO;
        _clearedOutside = YES;
        return;
    }
    // update the search results
    [self.searchResultsUpdater updateSearchResultsForSearchController:self];
}

@end

Quelques pièces à noter:

  1. J'ai mis la barre de recherche et la variable BOOL comme variables privées au lieu de propriétés, car
    • Ils sont plus légers que les propriétés privées.
    • Ils n'ont pas besoin d'être vus ou modifiés par le monde extérieur.
  2. Nous vérifions si la searchBar est le premier intervenant. Si ce n'est pas le cas, nous désactivons le contrôleur de recherche car le texte est vide et nous ne cherchons plus. Si vous (vraiment} _ voulez être sûr, vous pouvez également vous assurer que searchText.length == 0.
  3. searchBar:textDidChange: est appelé avant searchBarShouldBeginEditing:, raison pour laquelle nous l'avons traité dans cet ordre.
  4. Je mets à jour les résultats de la recherche chaque fois que le texte est modifié, mais vous pouvez déplacer le [self.searchResultsUpdater updateSearchResultsForSearchController:self]; vers searchBarSearchButtonClicked: si vous souhaitez que la recherche soit effectuée uniquement lorsque l'utilisateur appuie sur le bouton Search.
0
mikeho

Si vous appuyez sur le bouton d'effacement, searchText est vide. Pour cela, vous pouvez également rechercher du texte vide dans - (void)searchBar:(UISearchBar *)bar textDidChange:(NSString *)searchText:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if([searchText length] == 0)
    {
        [self dismissSearch];
    }
    else
    {
        self.searchResultsTable.hidden = YES;
        [self handleSearchForString:searchText];
    }
}

- (void)dismissSearch
{
    [self.searchBar performSelector: @selector(resignFirstResponder)
                  withObject: nil
                  afterDelay: 0.1];

    self.searchResultsTable.hidden = YES;
}
0
H K

Parmi les appels de délégués de la barre de recherche vous demande d’accepter le passage d’une ancienne valeur à une nouvelle - vous pouvez détecter que la nouvelle valeur est nulle, l’ancienne valeur étant non-nil et un indicateur indiquant que l’utilisateur n’a pas rien tapé depuis la dernière utilisation du clavier - puis dans ce cas, renoncez au premier répondant pour la barre de recherche. Pas sûr si le clavier va afficher momentanément si.

J'ai une situation très similaire et peut essayer moi-même.

Dans votre méthode endEditing, pourquoi ne pas effacer la barre UISearchBar et là? Etant donné que vous devez également démissionner du premier répondant, cela a du sens.

0
Corey Floyd

Une version rapide de la réponse de @boliva.

class MySearchContentController: UISearchBarDelegate {

    private var searchBarShouldBeginEditing = true

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        searchBarShouldBeginEditing = searchBar.isFirstResponder
    }

    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        defer {
            searchBarShouldBeginEditing = true
        }
        return searchBarShouldBeginEditing
    }
}
0
swordray