507 votes

Comment naviguer dans les champs de texte (boutons Suivant / Terminé)

Comment naviguer dans tous mes champs de texte avec le bouton "Suivant" du clavier de l'iPhone ?

Le dernier champ de texte doit fermer le clavier.

J'ai configuré les boutons IB (Next / Done) mais je suis maintenant bloqué.

J'ai implémenté l'action textFieldShouldReturn mais maintenant les boutons Next et Done ferment le clavier.

0 votes

Jetez un coup d'œil à ma réponse ci-dessous. D'après mon expérience, il s'agit d'une solution meilleure et plus complète que la réponse généralement donnée. Je n'ai aucune idée de la raison pour laquelle quelqu'un lui donnerait une note négative.

0 votes

J'ai le même problème mais avec Cordova, Phonegap. Y a-t-il un moyen de le résoudre ?

592voto

PeyloW Points 25312

Dans Cocoa pour Mac OS X, vous disposez de la chaîne de réponse suivante, qui vous permet de demander au champ de texte quel contrôle doit avoir le focus ensuite. C'est ce qui permet de tabuler entre les champs de texte. Mais comme les appareils iOS n'ont pas de clavier, mais seulement un clavier tactile, ce concept n'a pas survécu à la transition vers Cocoa Touch.

Cela peut être facilement fait de toute façon, avec deux hypothèses :

  1. Tous les "tabbable" UITextField se trouvent sur la même vue parentale.
  2. Leur "ordre de tabulation" est défini par la propriété "tag".

En partant de ce principe, vous pouvez remplacer textFieldShouldReturn : par textFieldShouldReturn: :

-(BOOL)textFieldShouldReturn:(UITextField*)textField
{
  NSInteger nextTag = textField.tag + 1;
  // Try to find next responder
  UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
  if (nextResponder) {
    // Found next responder, so set it.
    [nextResponder becomeFirstResponder];
  } else {
    // Not found, so remove keyboard.
    [textField resignFirstResponder];
  }
  return NO; // We do not want UITextField to insert line-breaks.
}

Si l'on ajoute un peu plus de code, les hypothèses peuvent également être ignorées.

Swift 4.0

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    let nextTag = textField.tag + 1
    // Try to find next responder
    let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!

    if nextResponder != nil {
        // Found next responder, so set it
        nextResponder?.becomeFirstResponder()
    } else {
        // Not found, so remove keyboard
        textField.resignFirstResponder()
    }

    return false
}

Si la vue principale du champ de texte est une UITableViewCell, le prochain répondeur sera

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!

6 votes

Parfois, il y a des boutons "suivant" et "précédent" en haut du clavier. Du moins dans le navigateur.

31 votes

Par souci de pédantisme, je tiens à dissiper certaines informations erronées dans ce billet. Sous OS X, l'ordre des tabulations est défini via la fonction setNextKeyView de NSView : (et non setNextResponder :) de NSView. La chaîne des répondeurs est distincte de cette méthode : il s'agit d'une hiérarchie d'objets répondeurs qui gèrent les actions "non ciblées", c'est-à-dire les actions envoyées au premier répondeur au lieu d'être envoyées directement à un objet contrôleur. La chaîne des répondeurs suit généralement la hiérarchie des vues, jusqu'à la fenêtre et son contrôleur, puis le délégué de l'application. Google "responder chain" pour de nombreuses informations.

0 votes

La vue d'ensemble du champ de texte sera un champ de type UITableViewCell . Vous devez accéder à la base de données UITableView et obtenir la cellule contenant le champ de texte recherché.

172voto

Michael G. Emmons Points 16681

Il existe un beaucoup plus élégante qui m'a époustouflé la première fois que je l'ai vue. Avantages :

  • Plus proche de l'implémentation des champs de texte OSX où un champ de texte sait où le focus doit aller ensuite
  • Ne repose pas sur la définition ou l'utilisation de balises - qui sont, IMO, fragiles pour ce cas d'utilisation.
  • Peut être étendu pour fonctionner avec les deux UITextField y UITextView ou tout contrôle d'interface utilisateur avec saisie au clavier
  • N'encombre pas votre contrôleur de vue avec du code délégué UITextField de type chaudière
  • Il s'intègre parfaitement à l'IB et peut être configuré à l'aide de l'option familière "glisser-déposer" pour connecter les prises.

Créer une sous-classe d'UITextField qui possède une fonction IBOutlet appelée nextField. Voici l'en-tête :

@interface SOTextField : UITextField

@property (weak, nonatomic) IBOutlet UITextField *nextField; 

@end

Et voici la mise en œuvre :

@implementation SOTextField

@end

Dans votre contrôleur de vue, vous créerez le fichier -textFieldShouldReturn: méthode de délégation :

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    if ([textField isKindOfClass:[SOTextField class]]) {
        UITextField *nextField = [(SOTextField *)textField nextField];

        if (nextField) {
            dispatch_async(dispatch_get_current_queue(), ^{
                [nextField becomeFirstResponder];
            });
        }
        else {
            [textField resignFirstResponder];
        }
    }

    return YES;
}

Dans l'IB, modifiez vos UITextFields pour utiliser l'élément SOTextField classe. Ensuite, toujours dans IB, définissez le délégué de chacun des "SOTextFields" à "File's Owner" (c'est-à-dire à l'endroit où vous avez placé le code de la méthode de délégué - textFieldShouldReturn). La beauté de cette conception réside dans le fait que vous pouvez maintenant simplement cliquer avec le bouton droit de la souris sur n'importe quel champ de texte et assigner la sortie nextField au champ de texte suivant. SOTextField vous voulez être le prochain à répondre.

Assigning nextField in IB

En outre, vous pouvez faire des choses intéressantes, comme mettre en boucle les champs de texte de sorte qu'après que le dernier ait perdu le focus, le premier recevra à nouveau le focus.

Il est facile d'étendre ce principe pour attribuer automatiquement le returnKeyType de la SOTextField à un UIReturnKeyNext s'il y a un nextField assigné -- une chose de moins à configurer manuellement.

0 votes

Je pense qu'il est plus facile de définir des balises que d'utiliser cette sous-classe. Mais c'est une alternative intéressante... merci

3 votes

J'aime beaucoup l'intégration de l'IB par rapport à l'utilisation des tags - en particulier avec les nouvelles versions de Xcode qui intègrent bien l'IB avec le reste de l'IDE - et le fait de ne pas avoir à encombrer mon contrôleur avec du code délégué textField est un bonus.

4 votes

Les étiquettes sont plus faciles, mais cette méthode est meilleure et plus appropriée à une bonne conception OO.

92voto

mxcl Points 5921

En voici une sans délégation :

tf1.addTarget(tf2, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit)
tf2.addTarget(tf3, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit)

ObjC :

[tf1 addTarget:tf2 action:@selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];
[tf2 addTarget:tf3 action:@selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];

Travaux utilisant le système (pour la plupart inconnu) UIControlEventEditingDidEndOnExit UITextField action.

Vous pouvez aussi facilement l'intégrer dans le storyboard, ce qui permet de ne pas déléguer les tâches. o est nécessaire.

Edit : en fait, je n'arrive pas à comprendre comment connecter cela dans le storyboard. becomeFirstResponder ne semble pas être une action proposée pour cet événement de contrôle, ce qui est dommage. Néanmoins, vous pouvez relier tous vos champs de texte à une action unique dans votre ViewController qui déterminera alors quel champ de texte doit être becomeFirstResponder en fonction de l'expéditeur (mais ce n'est pas aussi élégant que la solution programmatique ci-dessus, donc IMO le fait avec le code ci-dessus en viewDidLoad ).

19 votes

Une solution très simple et efficace. Le dernier de la chaîne peut même déclencher par exemple [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit]; . Merci @mxcl.

0 votes

Comment faire cela dans le storyboard ?

1 votes

C'est probablement la meilleure réponse.

78voto

Anth0 Points 921

Voici ma solution à ce problème.

Pour résoudre ce problème (et parce que je déteste dépendre des balises pour faire des choses), j'ai décidé d'ajouter une propriété personnalisée à l'objet UITextField. En d'autres termes, j'ai créé une catégorie sur l'objet UITextField comme ceci :

UITextField+Extended.h

@interface UITextField (Extended)

@property(retain, nonatomic)UITextField* nextTextField;

@end

UITextField+Extended.m

#import "UITextField+Extended.h"
#import <objc/runtime.h>

static char defaultHashKey;

@implementation UITextField (Extended)

- (UITextField*) nextTextField { 
    return objc_getAssociatedObject(self, &defaultHashKey); 
}

- (void) setNextTextField:(UITextField *)nextTextField{
    objc_setAssociatedObject(self, &defaultHashKey, nextTextField, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

Voici maintenant comment je l'utilise :

UITextField *textField1 = ...init your textfield
UITextField *textField2 = ...init your textfield
UITextField *textField3 = ...init your textfield

textField1.nextTextField = textField2;
textField2.nextTextField = textField3;
textField3.nextTextField = nil;

Et implémenter la méthode textFieldShouldReturn :

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {

    UITextField *next = theTextField.nextTextField;
    if (next) {
        [next becomeFirstResponder];
    } else {
        [theTextField resignFirstResponder];
    }

    return NO; 
}

J'ai maintenant une sorte de liste liée d'UITextField, chacun sachant qui est le suivant dans la ligne.

J'espère que cela vous aidera.

11 votes

C'est la meilleure solution et elle doit être choisie comme réponse.

4 votes

Cette solution a fonctionné pour moi, la seule chose que j'ai changée a été de faire le nextTextField et l'IBOutlet pour que je puisse mettre en place le chaînage à partir de mon Storyboard.

0 votes

Si je pouvais conserver mes réponses préférées, celle-ci en ferait partie. Merci pour cette solution propre !

14voto

Marquee Points 605

J'aime les solutions OO qui ont déjà été proposées par Anth0 et Answerbot. Cependant, je travaillais sur un petit POC rapide, donc je ne voulais pas encombrer les choses avec des sous-classes et des catégories.

Une autre solution simple consiste à créer un NSArray de champs et à rechercher le champ suivant lorsque vous appuyez sur next. Ce n'est pas une solution OO, mais elle est rapide, simple et facile à mettre en œuvre. De plus, vous pouvez voir et modifier l'ordre des champs d'un seul coup d'œil.

Voici mon code (basé sur d'autres réponses dans ce fil) :

@property (nonatomic) NSArray *fieldArray;

- (void)viewDidLoad {
    [super viewDidLoad];

    fieldArray = [NSArray arrayWithObjects: firstField, secondField, thirdField, nil];
}

- (BOOL) textFieldShouldReturn:(UITextField *) textField {
    BOOL didResign = [textField resignFirstResponder];
    if (!didResign) return NO;

    NSUInteger index = [self.fieldArray indexOfObject:textField];
    if (index == NSNotFound || index + 1 == fieldArray.count) return NO;

    id nextField = [fieldArray objectAtIndex:index + 1];
    activeField = nextField;
    [nextField becomeFirstResponder];

    return NO;
}
  • Je renvoie toujours NO car je ne veux pas qu'un saut de ligne soit inséré. Je tenais à le préciser, car lorsque je renvoyais YES, les champs suivants étaient automatiquement supprimés ou un saut de ligne était inséré dans mon TextView. Il m'a fallu un peu de temps pour m'en rendre compte.
  • activeField garde la trace du champ actif au cas où il serait nécessaire de faire défiler le champ pour le dégager du clavier. Si vous avez un code similaire, assurez-vous d'attribuer le champ actif avant de changer le premier répondeur. Le changement de premier répondant est immédiat et déclenche immédiatement l'événement KeyboardWasShown.

0 votes

C'est bien ! Simple et le code se trouve dans un seul modèle. Celui d'AnthO est très bien aussi.

0 votes

Gardez à l'esprit que vous risquez de conserver des cycles avec ce code. Votre fieldArray convertit les références faibles utilisées dans toutes vos IBOutlets en références fortes dans le tableau lui-même.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X