web-dev-qa-db-fra.com

Fermer le clavier tactile en dehors de UITextField

Je développe une application pour iPad comportant un grand nombre de UIViewControllers, UITableViews (avec des cellules avec accessoryViews sur UITextFields), etc., etc. De nombreux UIViewControllers apparaissent dans une hiérarchie de navigation.

UITextFields apparaît dans de nombreux endroits différents, notamment UITableViewCellaccessoryViews.

J'aimerais élaborer une stratégie efficace pour supprimer le clavier lorsque l'utilisateur touche en dehors de la variable UITextField en cours de modification. J'ai cherché des techniques de suppression du clavier, mais je n'ai pas encore trouvé de réponse qui explique comment une stratégie de suppression du clavier générale pourrait fonctionner.

Par exemple, j'aime cette approche, où le code suivant est ajouté à n'importe quel ViewController:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"* * * * * * * * *ViewControllerBase touchesBegan");

    [self.view endEditing:YES]; // dismiss the keyboard

    [super touchesBegan:touches withEvent:event];
}

... mais cette technique ne traite pas les situations où, par exemple, un contact se produit dans un UITableView qui est affiché. Donc, il me faudrait ajouter du code pour appeler endEditing quand un UITableView est touché, etc., etc. Ce qui signifie que mon application sera généreusement saupoudrée de beaucoup de code pour ignorer le clavier lorsque plusieurs autres UIElements seront touchés.

Je suppose que je pourrais simplement essayer d’identifier tous les endroits où les touches doivent être interceptées et le clavier ignoré, mais il me semble qu’il pourrait exister un meilleur modèle de conception quelque part pour gérer les événements de désactivation de clavier iOS.

Quelqu'un peut-il partager ses expériences en la matière et recommander une technique spécifique pour gérer de manière générique le licenciement du clavier dans une application entière?

Merci beaucoup

26
whatdoesitallmean

Votre hiérarchie de vues réside dans une UIWindow. La UIWindow est responsable du transfert des événements tactiles vers la vue correcte dans sa méthode sendEvent:. Faisons une sous-classe de UIWindow pour remplacer sendEvent:.

@interface MyWindow : UIWindow
@end

La fenêtre nécessitera une référence au premier intervenant actuel, le cas échéant. Vous pouvez décider d'utiliser également UITextView, nous observerons donc les notifications des champs de texte et des vues de texte.

@implementation MyWindow {
    UIView *currentFirstResponder_;
}

- (void)startObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)stopObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)observeBeginEditing:(NSNotification *)note {
    currentFirstResponder_ = note.object;
}

- (void)observeEndEditing:(NSNotification *)note {
    if (currentFirstResponder_ == note.object) {
        currentFirstResponder_ = nil;
    }
}

La fenêtre commencera à observer les notifications lors de son initialisation et s’arrêtera lors de sa désallocation:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    [self startObservingFirstResponder];
}

- (void)dealloc {
    [self stopObservingFirstResponder];
}

Nous remplacerons sendEvent: pour «ajuster» le premier intervenant en fonction de l'événement, puis nous appellerons le sendEvent: de super pour envoyer l'événement normalement.

- (void)sendEvent:(UIEvent *)event {
    [self adjustFirstResponderForEvent:event];
    [super sendEvent:event];
}

Nous n'avons rien à faire avec le premier intervenant s'il n'y a pas de premier intervenant. S'il y a un premier intervenant et qu'il contient une touche, nous ne voulons pas le forcer à démissionner. (Rappelez-vous qu'il peut y avoir plusieurs contacts simultanément!) S'il y a un premier intervenant et qu'un nouveau contact apparaît dans une autre vue pouvant devenir le premier intervenant, le système le gérera automatiquement de manière appropriée. Mais s'il y a un premier intervenant, et qu'il ne contient aucune touche, et qu'un nouveau contact apparaît dans une vue que ne peut pas devenir le premier intervenant, nous voulons que le premier intervenant démissionne.

- (void)adjustFirstResponderForEvent:(UIEvent *)event {
    if (currentFirstResponder_
        && ![self eventContainsTouchInFirstResponder:event]
        && [self eventContainsNewTouchInNonresponder:event]) {
        [currentFirstResponder_ resignFirstResponder];
    }
}

Il est facile de signaler si un événement contient une touche dans le premier intervenant:

- (BOOL)eventContainsTouchInFirstResponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.view == currentFirstResponder_)
            return YES;
    }
    return NO;
}

Indiquer si un événement contient une nouvelle touche dans une vue qui ne peut pas devenir le premier intervenant est presque aussi simple:

- (BOOL)eventContainsNewTouchInNonresponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.phase == UITouchPhaseBegan && ![touch.view canBecomeFirstResponder])
            return YES;
    }
    return NO;
}

@end

Une fois que vous avez implémenté cette classe, vous devez modifier votre application pour l’utiliser au lieu de UIWindow.

Si vous créez votre UIWindow dans application:didFinishLaunchingWithOptions:, vous devez #import "MyWindow.h" en haut de votre AppDelegate.m, puis modifiez application:didFinishLaunchingWithOptions: pour créer une MyWindow au lieu de UIWindow.

Si vous créez votre UIWindow dans une nib, vous devez définir la classe personnalisée de la fenêtre sur MyWindow dans la nib.

26
rob mayoff

Voici un moyen beaucoup plus simple et efficace de gérer cela. Cela fonctionnera pour n'importe quel UITextField dans votre contrôleur de vue. Vous pouvez même l'ajouter à votre contrôleur de vue de base (si vous en avez un) et cela fonctionnera à merveille.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [[event allTouches] anyObject];

    if (![[touch view] isKindOfClass:[UITextField class]]) {
        [self.view endEditing:YES];
    }
    [super touchesBegan:touches withEvent:event];
}
23
Yas T.

J'utilise normalement ce qui suit

Premier:

Inclure l’extension suivante dans votre fichier d’implémentation, findFirstResponder vous aidera à trouver le premier utilisateur

@implementation UIView (FindFirstResponder)
- (UIView*)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;     
    }
    for (UIView *subView in self.subviews) {
        UIView *responder = [subView findFirstResponder];
        if (responder)
            return responder;
    }

    return nil;
}
@end

Puis dans votre contrôleur de vue viewDidLoad, ajoutez ce qui suit

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(keyboardDidShow:) 
                   name:UIKeyboardDidShowNotification 
                 object:nil];

    [center addObserver:self selector:@selector(keyboardDidHide:) 
                   name:UIKeyboardWillHideNotification 
                 object:nil];
    // Do any additional setup after loading the view, typically from a nib.
}

Les fonctions de notification seront comme ça

- (void) keyboardDidShow:(NSNotification*)notification
{    
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;


    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];
    button.tag = 111;
    UIView *currentResponer = [self.view findFirstResponder];
    [button addTarget:currentResponer action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponer];
}

- (void) keyboardDidHide:(NSNotification*)notification
{
    [[self.view viewWithTag:111] removeFromSuperview];
}

Quand le clavier montre, j’ajoute une UIButton sous le premier répondant actuel, cette action du bouton cachera le clavier, 

Une limitation ici est que la UITextField doit être dans self.view mais vous pouvez adapter cette technique à votre besoin avec quelques modifications, en espérant que cela vous aide

3
Omar Abdelhafith

En fait, j'aime beaucoup la technique d'Omar - cela fonctionne à merveille - mais j'ai ajouté quelques lignes dans mon cas.

- (void)keyboardDidShow:(NSNotification*)notification
{
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;

    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];

    [button setAlpha:0.5f];
    button.tag = 111;
    UIView *currentResponder = [self.view findFirstResponder];

    [self.view bringSubviewToFront:currentResponder];

    if (currentResponder == _descriptionTextView) [_descriptionTextView setTextColor:[UIColor whiteColor]];

    [button addTarget:currentResponder action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponder];
}

J'ai ajouté trois lignes à sa solution (voir ci-dessus):

  1. Réglez l'alpha du bouton sur 0.6 afin que je puisse voir une partie de mon ViewController derrière celui-ci. 
  2. Placez le répondeur current à l'avant, De sorte qu'aucune de mes autres vues ne se trouve devant le bouton.
  3. Changez la couleur de mon UITextView pour que le texte soit plus clair (mon UITextView a un arrière-plan clair, le texte ne montrait donc pas clairement ).

Quoi qu'il en soit, seuls des changements mineurs, mais j'espère que cela aidera. Merci Omar.

2
siburb

Dans Swift 3

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    self.view.endEditing(true)
}
1
Gagandeep Gambhir

Pour démissionner du premier répondant actuel, j'ai essayé d'utiliser endEditing, mais des problèmes se sont posés pour définir un premier répondant par la suite. J'ai ensuite utilisé une méthode récursive pendant un certain temps pour trouver le premier répondant dans une méthode de catégorie UIView:

- (UIView *)getFirstResponder {

    if (self.isFirstResponder) {
        return self;
    }

    for (UIView *subView in self.subviews) {
        UIView *firstResponder = [subView getFirstResponder];

        if (firstResponder != nil) {
            return firstResponder;
        }
    }

    return nil;
}

Une fois que le répondeur est renvoyé, je peux simplement appeler resignFirstResponder.

Mais juste aujourd’hui, un de mes amis au travail m’a montré une méthode encore meilleure ici: http://overooped.com/post/28510603744/i-had-completely-forgotten-abgot-nil-targeted

Appelez ceci:

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

Un paquebot génial!

1
Matt Becker

Ajoutez une méthode de notification "textFieldDidChange" au contrôle de champ de texte.

[textField addTarget: action auto: @selector (textFieldDidChange :) pourControlEvents: UIControlEventEditingDidEnd];

travaille pour moi

0
alvarodoune

Peut-être que je ne comprends pas ce que vous voulez, mais la méthode - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch peut vous aider à ignorer le clavier

0
The iOSDev