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


487

Comment puis-je parcourir tous mes champs de texte avec le bouton "Suivant" du clavier de l'iPhone?

Le dernier champ de texte devrait fermer le clavier.

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

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


Veuillez consulter ma réponse ci-dessous. D'après mon expérience, c'est une meilleure solution, plus complète, que la réponse généralement donnée. Je ne sais pas pourquoi quelqu'un lui donnerait une note négative.
memmons

J'ai le même problème mais avec Cordova, Phonegap Existe-t-il des moyens de le résoudre?

Réponses:


578

Dans Cocoa pour Mac OS X, vous avez la chaîne de répondeurs suivante, où vous pouvez demander au champ de texte quel contrôle doit avoir le focus ensuite. C'est ce qui fait que la tabulation entre les champs de texte fonctionne. Mais comme les appareils iOS n'ont pas de clavier, seulement tactile, ce concept n'a pas survécu à la transition vers Cocoa Touch.

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

  1. Tous les "tabulations" UITextFieldsont sur la même vue parent.
  2. Leur "ordre de tabulation" est défini par la propriété tag.

En supposant cela, vous pouvez remplacer textFieldShouldReturn: comme ceci:

-(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.
}

Ajoutez un peu plus de code et 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 d'ensemble du champ de texte sera un UITableViewCell, le prochain répondeur sera

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

6
Parfois, il y a des boutons "suivant" et "précédent" sur le dessus du clavier. Au moins dans le navigateur.
Tim Büthe

31
Juste pour être pédant, je veux clarifier certaines informations erronées dans ce post. Sous OS X, l'ordre des onglets est configuré via la méthode setNextKeyView de NSView (et non la méthode setNextResponder :). La chaîne de répondeurs est distincte de celle-ci: il s'agit d'une hiérarchie d'objets répondeurs qui gèrent des actions "non ciblées", par exemple des actions envoyées au premier répondant au lieu de directement à un objet contrôleur. La chaîne de 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 "chaîne de répondeurs" pour de nombreuses informations.
davehayden

La vue d'ensemble du champ de texte sera a UITableViewCell. Vous devez accéder à l'associé UITableViewet obtenir la cellule contenant le champ de texte que vous recherchez.
Michael Mior

13
N'oubliez pas d'ajouter le UITextFieldDelegateet de définir les délégués des champs de texte.
Sal

1
Rappel amical pour définir l'option TAG à partir du storyboard (dans mon cas). Vous devez définir manuellement la balise TextFields dans l'ordre croissant. (Le premier est 0, le second est 1, etc.) pour que cette solution fonctionne.
Joakim

170

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

  • Plus proche de l'implémentation de champ 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 fragiles à l'OMI pour ce cas d'utilisation
  • Peut être étendu pour fonctionner avec les deux commandes UITextFieldet UITextView- ou avec n'importe quel contrôle d'interface utilisateur d'entrée de clavier
  • N'encombre pas votre contrôleur de vue avec le code délégué UITextField standard
  • S'intègre parfaitement à IB et peut être configuré via l'option glisser-déposer familière pour connecter les prises.

Créez une sous-classe UITextField qui possède une IBOutletpropriété appelée nextField. Voici l'en-tête:

@interface SOTextField : UITextField

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

@end

Et voici l'implémentation:

@implementation SOTextField

@end

Dans votre contrôleur de vue, vous allez créer la -textFieldShouldReturn:méthode déléguée:

- (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 IB, modifiez vos UITextFields pour utiliser la SOTextFieldclasse. Ensuite, également dans IB, définissez le délégué pour chacun des 'SOTextFields'to' File's Owner '(qui est juste là où vous mettez le code pour la méthode déléguée - textFieldShouldReturn). La beauté de cette conception est que vous pouvez maintenant simplement cliquer avec le bouton droit sur n'importe quel champ textField et affecter la sortie nextField au prochain SOTextFieldobjet dont vous voulez être le prochain intervenant.

Affectation de nextField dans IB

De plus, vous pouvez faire des choses sympas comme boucler les textFields pour qu'après que le dernier ait perdu le focus, le premier recevra à nouveau le focus.

Cela peut facilement être étendu pour attribuer automatiquement returnKeyTypele SOTextFieldà un UIReturnKeyNexts'il y a un nextField affecté - une chose de moins à configurer manuellement.


3
J'aime vraiment l'intégration de IB plutôt que d'utiliser des balises - en particulier avec les nouvelles versions de Xcode qui s'intègre bien à IB avec le reste de l'IDE - et ne pas avoir à encombrer mon contrôleur avec du code délégué textField standard est un bonus.
memmons

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

1
J'aime vraiment cette solution, mais j'utilise chaque champ de texte dans des vues de cellule distinctes dans a UITableView. Bien que j'ai des IBOutletpropriétés sur le contrôleur de vue pour chaque champ de texte, il ne me permet pas de lier la nextFieldpropriété à ces propriétés sur le propriétaire du fichier. Un conseil comment je peux le faire fonctionner dans ce scénario?
Jay Imerman

1
@JayImerman IB ne vous permettra pas de connecter des prises entre les cellules. Ainsi, IB sera un no-go pour ce scénario. Cependant, vous pouvez simplement connecter les propriétés nextField à l'aide de code. textField1.nextField = textField2; textField2.nextField = textField3;, etc.
memmons

2
Y a-t-il une chance que cette réponse soit mise à jour pour les nouvelles versions de xcode et ios?
lostintranslation

82

En voici un 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];

Fonctionne en utilisant l'action (principalement inconnue) UIControlEventEditingDidEndOnExit UITextField.

Vous pouvez également facilement le connecter dans le storyboard, donc aucune délégation ou code n'est requis.

Edit: en fait, je ne peux pas comprendre comment connecter cela dans le storyboard. becomeFirstResponderne semble pas être une action offerte pour cet événement de contrôle, ce qui est dommage. Néanmoins, vous pouvez connecter tous vos champs de texte à une seule action dans votre ViewController qui détermine ensuite le champ de texte à becomeFirstResponderbasé sur l'expéditeur (bien qu'il ne soit pas aussi élégant que la solution de programmation ci-dessus, IMO le fait avec le code ci-dessus viewDidLoad).


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

Comment feriez-vous cela dans le storyboard?
Pedro Borges

C'est probablement la meilleure réponse ici.
daspianist

Cela a parfaitement fonctionné pour moi, même dans Swift 2. C'est une solution beaucoup plus simple et plus rapide que d'ajouter des délégués.
tonne

2
Swift 4: txtFirstname.addTarget (txtLastName, action: #selector (becomeFirstResponder), pour: UIControlEvents.editingDidEndOnExit)
Realtimez

78

Voici ma solution pour ce problème.

Pour résoudre ce problème (et parce que je déteste me fier aux 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 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

Maintenant, voici 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émentez 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 chaînée d'UITextField, chacun sachant qui est le prochain dans la ligne.

J'espère que cela vous aidera.


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

4
Cette solution a fonctionné pour moi, la seule chose que j'ai changé était de créer le nextTextField et l'IBOutlet afin que je puisse configurer le chaînage à partir de mon Storyboard.
zachzurn

Si je pouvais enregistrer mes réponses préférées, ce serait l'une d'entre elles. Merci pour la solution propre!
Matt Becker

1
Notez que si IB est votre truc, vous pouvez également définir ces propriétés comme IBOutlets. . Soyez prudent avec les collisions de noms - Apple pourrait éventuellement l'ajouter à UIResponder.
Jasper Blues

Cette solution est-elle compatible avec le constructeur d'interface? Je veux dire peut-il utiliser des IBOutlets et des références de storyboard au lieu de définir le nextTextField par programme? IB ne semble pas vous permettre de définir la classe UITextField sur une catégorie.
Pedro Borges

46

Une extension rapide qui applique la réponse de mxcl pour rendre cela particulièrement facile (adapté à swift 2.3 par Traveler):

extension UITextField {
    class func connectFields(fields:[UITextField]) -> Void {
        guard let last = fields.last else {
            return
        }
        for i in 0 ..< fields.count - 1 {
            fields[i].returnKeyType = .Next
            fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)
        }
        last.returnKeyType = .Done
        last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), forControlEvents: .EditingDidEndOnExit)
    }
}

C'est simple à utiliser:

UITextField.connectFields([field1, field2, field3])

L'extension définira le bouton de retour sur "Suivant" pour tous sauf le dernier champ et sur "Terminé" pour le dernier champ, et changera le focus / fermera le clavier lorsque ceux-ci seront tapés.

Rapide <2,3

extension UITextField {
    class func connectFields(fields:[UITextField]) -> Void {
        guard let last = fields.last else {
            return
        }
        for var i = 0; i < fields.count - 1; i += 1 {
            fields[i].returnKeyType = .Next
            fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)
        }
        last.returnKeyType = .Done
        last.addTarget(last, action: "resignFirstResponder", forControlEvents: .EditingDidEndOnExit)
    }
}

SWIFT 3: utilisez comme ceci -

UITextField.connectFields(fields: [field1, field2])

Extension:
    extension UITextField {
        class func connectFields(fields:[UITextField]) -> Void {
            guard let last = fields.last else {
                return
            }
            for i in 0 ..< fields.count - 1 {
                fields[i].returnKeyType = .next
                fields[i].addTarget(fields[i+1], action: #selector(UIResponder.becomeFirstResponder), for: .editingDidEndOnExit)
            }
            last.returnKeyType = .go
            last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), for: .editingDidEndOnExit)
        }
    }

5
BTW, si vous voulez que Done effectue ensuite une action, vous pouvez simplement faire en sorte que votre contrôleur de vue soit conforme UITextFieldDelegateet implémenté func textFieldDidEndEditing(textField: UITextField). Rentrez tôt si textField != field3.
Ryan

25

Une manière plus cohérente et robuste consiste à utiliser NextResponderTextField. Vous pouvez le configurer totalement à partir du générateur d'interface sans avoir besoin de définir le délégué ou d'utiliser view.tag.

Tout ce que vous devez faire c'est

  1. Définissez le type de classe de votre UITextFieldêtreNextResponderTextField entrez la description de l'image ici
  2. Ensuite, définissez la sortie de la nextResponderFieldpour pointer vers le répondeur suivant, il peut être n'importe quoi UITextFieldou n'importe quelle UIRespondersous-classe. Il peut également s'agir d'un TouchUpInsidebouton UIB et la bibliothèque est suffisamment intelligente pour déclencher l' événement du bouton uniquement s'il est activé. entrez la description de l'image ici entrez la description de l'image ici

Voici la bibliothèque en action:

entrez la description de l'image ici


Oui. Fonctionne très bien avec Swift 3.1, une solution très élégante, à quel point IB devrait fonctionner hors de la boîte.
Richard

Désolé, je n'ai pas compris la question.
mohamede1945

1
NextResponderTextField a fonctionné pour moi. Il était rapide et facile à utiliser, un seul fichier rapide et faisait exactement ce dont j'avais besoin sans tracas.
Pat McG

Cela fonctionne très bien! Remarque: j'ai dû modifier la touche Retour manuellement pour qu'elle affiche "Suivant" et "Terminé" comme dans l'exemple.
Mike Miller

14

J'aime les solutions OO déjà proposées par Anth0 et Answerbot. Cependant, je travaillais sur un POC rapide et petit, 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 suivant. Pas une solution OO, mais rapide, simple et facile à mettre en œuvre. De plus, vous pouvez voir et modifier la commande en un coup d'œil.

Voici mon code (construit 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 retourne toujours NON car je ne veux pas qu'un saut de ligne soit inséré. Je pensais juste le souligner car lorsque je retournais OUI, il quitterait automatiquement les champs suivants ou insèrerait un saut de ligne dans mon TextView. Cela m'a pris un peu de temps pour comprendre cela.
  • activeField garde une trace du champ actif au cas où le défilement est nécessaire pour dégager le champ 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épondeur est immédiat et déclenche immédiatement l'événement KeyboardWasShown.

Agréable! Simple et le code est tout en un modèle. AnthO's est très bien aussi.
Seamus

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

11

Voici une implémentation de tabulation à l'aide d'une catégorie sur UIControl. Cette solution présente tous les avantages des méthodes de Michael et Anth0, mais fonctionne pour tous les UIControls, pas seulement pour UITextFields. Il fonctionne également de manière transparente avec Interface Builder et les storyboards.

Source et exemple d'application: référentiel GitHub pour UIControlsWithTabbing

Usage:

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField transferFirstResponderToNextControl];
    return NO;
}

Affectation de nextControl dans Interface Builder

Entête:

//
// UIControl+NextControl.h
// UIControlsWithTabbing
//

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

@property (nonatomic, weak) IBOutlet UIControl *nextControl;

- (BOOL)transferFirstResponderToNextControl;

@end

La mise en oeuvre:

#import "UIControl+NextControl.h"
#import <objc/runtime.h>

static char defaultHashKey;

@implementation UIControl (NextControl)

- (UIControl *)nextControl
{
    return objc_getAssociatedObject(self, &defaultHashKey);
}

- (void)setNextControl:(UIControl *)nextControl
{
    objc_setAssociatedObject(self, &defaultHashKey, nextControl, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)transferFirstResponderToNextControl
{
    if (self.nextControl)
    {
        [self.nextControl becomeFirstResponder];

        return YES;
    }

    [self resignFirstResponder];

    return NO;
}

@end

1
Parfois, le widget suivant n'est pas un contrôle, c'est-à-dire UITextView et vous souhaiterez peut-être y tabuler également. Envisagez de changer le type de UITextField en UIResponder.
Pedro Borges

1
Fonctionne très bien et avec une documentation fantastique. Je souhaite que je descende dans cette question il y a plusieurs heures. :)
Javier Cadiz

10

J'ai essayé de nombreux codes et enfin, cela a fonctionné pour moi dans Swift 3.0 Latest [mars 2017]

La ViewControllerclasse doit être héritée de UITextFieldDelegatepour que ce code fonctionne.

class ViewController: UIViewController,UITextFieldDelegate  

Ajoutez le champ Texte avec le numéro de balise approprié et ce numéro de balise est utilisé pour prendre le contrôle du champ de texte approprié en fonction du numéro de balise incrémentiel qui lui est attribué.

override func viewDidLoad() {
    userNameTextField.delegate = self
    userNameTextField.tag = 0
    userNameTextField.returnKeyType = UIReturnKeyType.next
    passwordTextField.delegate = self
    passwordTextField.tag = 1
    passwordTextField.returnKeyType = UIReturnKeyType.go
}

Dans le code ci-dessus, returnKeyType = UIReturnKeyType.nextoù fera la touche de retour du clavier à afficher, car Nextvous avez également d'autres options, Join/Goetc., en fonction de votre application, modifiez les valeurs.

Il textFieldShouldReturns'agit d'une méthode contrôlée par UITextFieldDelegate et nous avons ici la sélection de champ suivante basée sur l'incrémentation de la valeur de la balise

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
        nextField.becomeFirstResponder()
    } else {
        textField.resignFirstResponder()
        return true;
    }
    return false
 }

Cela fonctionne, sauf si le champ de saisie est UITextView. Une idée de comment faire cela en combinaison?
T9b

@ T9b De quoi avez-vous besoin exactement? quel type de contrôle utilisez-vous dans votre application?
BHUVANESH MOHANKUMAR

Il existe deux types de UITextFieldsaisie de texte, le premier est (entrez une seule ligne de texte), l'autre est UITextView(entrez plusieurs lignes de texte). UITextViewne répond évidemment pas à ce code mais il peut être utilisé dans les formulaires.
T9b

Oui cette partie du code pour UITextField uniquement et avez-vous essayé de modifier l'événement pour UITextView?
BHUVANESH MOHANKUMAR

9

Après avoir quitté un champ de texte, vous appelez [otherTextField devientFirstResponder] et le champ suivant obtient le focus.

Cela peut en fait être un problème délicat à résoudre car, souvent, vous souhaiterez également faire défiler l'écran ou ajuster la position du champ de texte afin qu'il soit facile à voir lors de la modification. Assurez-vous simplement de faire beaucoup de tests en entrant et en sortant des champs de texte de différentes manières et en partant tôt (donnez toujours à l'utilisateur la possibilité de fermer le clavier au lieu d'aller au champ suivant, généralement avec "Terminé" dans la barre de navigation)


9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}

Vous ne devez pas utiliser de balises à cet effet.
Christian Schnorr

7

Une méthode très simple pour fermer le clavier lorsque vous appuyez sur le bouton «Terminé» est:

Créer une nouvelle IBAction dans l'en-tête

- (IBAction)textFieldDoneEditing:(id)sender;

Dans le fichier d'implémentation (fichier .m), ajoutez la méthode suivante:

- (IBAction)textFieldDoneEditing:(id)sender 
{ 
  [sender resignFirstResponder];
}

Ensuite, lorsque vous venez de lier l'IBAction au champ de texte - liez à l'événement 'Did End On Exit'.


1
Vous êtes les bienvenus, il ne navigue pas dans les champs de texte mais ferme le clavier et vous permet de sélectionner un autre champ.
jcrowson

7

Définissez d'abord la touche de retour du clavier dans xib, sinon vous pouvez écrire du code viewdidload:

passWord.returnKeyType = UIReturnKeyNext;

-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
    if(textField == eMail) {
        [textField resignFirstResponder];
        [userName becomeFirstResponder];
    }
    if (textField==userName) {
        [textField resignFirstResponder];
        [passWord becomeFirstResponder];
    }
    if (textField==passWord) {
        [textField resignFirstResponder];
        [country becomeFirstResponder];
    }
    if (textField==country) {
        [textField resignFirstResponder];
    }
    return YES;
}

7

Je suis surpris du nombre de réponses qui ne parviennent pas à comprendre un concept simple: la navigation dans les contrôles de votre application n'est pas quelque chose que les vues elles-mêmes devraient faire. C'est le travail du contrôleur de décider quel contrôle faire le prochain premier répondant.

De plus, la plupart des réponses ne s'appliquent qu'à la navigation vers l'avant, mais les utilisateurs peuvent également vouloir revenir en arrière.

Voici donc ce que j'ai trouvé. Votre formulaire doit être géré par un contrôleur de vue, et les contrôleurs de vue font partie de la chaîne de répondeurs. Vous êtes donc parfaitement libre d'implémenter les méthodes suivantes:

#pragma mark - Key Commands

- (NSArray *)keyCommands
{
    static NSArray *commands;

    static dispatch_once_t once;
    dispatch_once(&once, ^{
        UIKeyCommand *const forward = [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(tabForward:)];
        UIKeyCommand *const backward = [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(tabBackward:)];

        commands = @[forward, backward];
    });

    return commands;
}

- (void)tabForward:(UIKeyCommand *)command
{
    NSArray *const controls = self.controls;
    UIResponder *firstResponder = nil;

    for (UIResponder *const responder in controls) {
        if (firstResponder != nil && responder.canBecomeFirstResponder) {
            [responder becomeFirstResponder]; return;
        }
        else if (responder.isFirstResponder) {
            firstResponder = responder;
        }
    }

    [controls.firstObject becomeFirstResponder];
}

- (void)tabBackward:(UIKeyCommand *)command
{
    NSArray *const controls = self.controls;
    UIResponder *firstResponder = nil;

    for (UIResponder *const responder in controls.reverseObjectEnumerator) {
        if (firstResponder != nil && responder.canBecomeFirstResponder) {
            [responder becomeFirstResponder]; return;
        }
        else if (responder.isFirstResponder) {
            firstResponder = responder;
        }
    }

    [controls.lastObject becomeFirstResponder];
}

Une logique supplémentaire pour faire défiler les répondeurs hors écran visibles à l'avance peut s'appliquer.

Un autre avantage de cette approche est que vous n'avez pas besoin de sous-classer toutes sortes de contrôles que vous souhaitez afficher (comme les UITextFields), mais que vous pouvez gérer la logique au niveau du contrôleur, où, soyons honnêtes, est le bon endroit pour le faire .


Je trouve votre réponse la plus pertinente, mais j'ai remarqué quelque chose de drôle que vous pouvez peut-être éclaircir. Lorsque vous utilisez UITextFields qui ne sont pas gérés par un UIViewController, appuyer sur la touche de tabulation fera du UITextField suivant le premier répondeur par défaut. Cependant, lorsque chacun est géré par un VC, ce n'est pas vrai. Mes VC ne font rien, mais la fonctionnalité de tabulation intégrée a disparu. J'ai remarqué que keyCommands du VC finit par être ajouté à la chaîne de répondeur de la vue. Cela m'amène à croire que si vous utilisez un VC, il est obligatoire d'implémenter keyCommands.
Joey Carson

4

J'ai ajouté à la réponse de PeyloW au cas où vous cherchez à implémenter une fonctionnalité de bouton précédent / suivant:

- (IBAction)moveThroughTextFields:(UIBarButtonItem *)sender 
{
    NSInteger nextTag;
    UITextView *currentTextField = [self.view findFirstResponderAndReturn];

    if (currentTextField != nil) {
        // I assigned tags to the buttons.  0 represent prev & 1 represents next
        if (sender.tag == 0) {
            nextTag = currentTextField.tag - 1;

        } else if (sender.tag == 1) {
            nextTag = currentTextField.tag + 1;
        }
    }
    // Try to find next responder
    UIResponder* nextResponder = [self.view viewWithTag:nextTag];
    if (nextResponder) {
        // Found next responder, so set it.
        // I added the resign here in case there's different keyboards in place.
        [currentTextField resignFirstResponder];
        [nextResponder becomeFirstResponder];
    } else {
        // Not found, so remove keyboard.
        [currentTextField resignFirstResponder];

    }
}

Où vous sous-classez l'UIV comme ceci:

@implementation UIView (FindAndReturnFirstResponder)
- (UITextView *)findFirstResponderAndReturn
{
    for (UITextView *subView in self.subviews) {
        if (subView.isFirstResponder){
            return subView;
        }
    }
    return nil;
}
@end

4

Salut à tous s'il vous plaît voir celui-ci

- (void)nextPrevious:(id)sender
{

  UIView *responder = [self.view findFirstResponder];   

  if (nil == responder || ![responder isKindOfClass:[GroupTextField class]]) {
    return;
  }

  switch([(UISegmentedControl *)sender selectedSegmentIndex]) {
    case 0:
      // previous
      if (nil != ((GroupTextField *)responder).previousControl) {
        [((GroupTextField *)responder).previousControl becomeFirstResponder];
        DebugLog(@"currentControl: %i previousControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).previousControl.tag);
      }
      break;
    case 1:
      // next
      if (nil != ((GroupTextField *)responder).nextControl) {
        [((GroupTextField *)responder).nextControl becomeFirstResponder];
        DebugLog(@"currentControl: %i nextControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).nextControl.tag);
      }     
      break;    
  }
}

Au lieu du répondeur UIView *, il serait plus précis d'utiliser le répondeur UIRespoder *
Pedro Borges


4

Je viens de créer un nouveau pod lorsque j'ai affaire à ce truc GNTextFieldsCollectionManager . Il gère automatiquement le prochain / dernier problème textField et est très facile à utiliser:

[[GNTextFieldsCollectionManager alloc] initWithView:self.view];

Récupère tous les champs de texte triés en apparaissant dans la hiérarchie des vues (ou par balises), ou vous pouvez spécifier votre propre tableau de textFields.


4

Solution dans Swift 3.1, après avoir connecté vos champs de texte IBOutlets définissez votre délégué de champs de texte dans viewDidLoad, puis parcourez votre action dans textFieldShouldReturn

class YourViewController: UIViewController,UITextFieldDelegate {

        @IBOutlet weak var passwordTextField: UITextField!
        @IBOutlet weak var phoneTextField: UITextField!

        override func viewDidLoad() {
            super.viewDidLoad()
            self.passwordTextField.delegate = self
            self.phoneTextField.delegate = self
            // Set your return type
            self.phoneTextField.returnKeyType = .next
            self.passwordTextField.returnKeyType = .done
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool{
            if textField == self.phoneTextField {
                self.passwordTextField.becomeFirstResponder()
            }else if textField == self.passwordTextField{
                // Call login api
                self.login()
            }
            return true
        }

    }

4

Si quelqu'un veut comme ça. Je pense que c'est le plus proche des exigences demandées en question

entrez la description de l'image ici

Voici comment j'ai implémenté celui-ci

Ajoutez une vue accessoire pour chaque champ de texte pour lequel vous souhaitez effectuer la configuration, en utilisant

func setAccessoryViewFor(textField : UITextField)    {
    let toolBar = UIToolbar()
    toolBar.barStyle = .default
    toolBar.isTranslucent = true
    toolBar.sizeToFit()

    // Adds the buttons

    // Add previousButton
    let prevButton = UIBarButtonItem(title: "<", style: .plain, target: self, action: #selector(previousPressed(sender:)))
    prevButton.tag = textField.tag
    if getPreviousResponderFor(tag: textField.tag) == nil {
        prevButton.isEnabled = false
    }

    // Add nextButton
    let nextButton = UIBarButtonItem(title: ">", style: .plain, target: self, action: #selector(nextPressed(sender:)))
    nextButton.tag = textField.tag
    if getNextResponderFor(tag: textField.tag) == nil {
        nextButton.title = "Done"
    }

    let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    toolBar.setItems([prevButton,spaceButton,nextButton], animated: false)
    toolBar.isUserInteractionEnabled = true
    textField.inputAccessoryView = toolBar
}

Utilisez les fonctions suivantes pour gérer les taps

func nextPressed(sender : UIBarButtonItem) {
    if let nextResponder = getNextResponderFor(tag: sender.tag) {
        nextResponder.becomeFirstResponder()
    } else {
        self.view.endEditing(true)
    }

}

func previousPressed(sender : UIBarButtonItem) {
    if let previousResponder = getPreviousResponderFor(tag : sender.tag)  {
        previousResponder.becomeFirstResponder()
    }
}

func getNextResponderFor(tag : Int) -> UITextField? {
    return self.view.viewWithTag(tag + 1) as? UITextField
}

func getPreviousResponderFor(tag : Int) -> UITextField? {
    return self.view.viewWithTag(tag - 1) as? UITextField
}

Vous devrez donner aux balises textFields dans l'ordre dans lequel vous souhaitez que le bouton suivant / précédent réponde.


3

Je préfère plutôt:

@interface MyViewController : UIViewController
@property (nonatomic, retain) IBOutletCollection(UIView) NSArray *inputFields;
@end

Dans le fichier NIB, je raccorde les textFields dans l'ordre souhaité à ce tableau inputFields. Après cela, je fais un test simple pour l'index de l'UITextField qui signale que l'utilisateur a tapé sur retour:

// for UITextField
-(BOOL)textFieldShouldReturn:(UITextField*)textField {
    NSUInteger index = [_inputFields indexOfObject:textField];
    index++;
    if (index < _inputFields.count) {
        UIView *v = [_inputFields objectAtIndex:index];
        [v becomeFirstResponder];
    }
    return NO;
}

// for UITextView
-(BOOL)textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
    if ([@"\n" isEqualToString:text]) {
        NSUInteger index = [_inputFields indexOfObject:textView];
        index++;
        if (index < _inputFields.count) {
            UIView *v = [_inputFields objectAtIndex:index];
            [v becomeFirstResponder];
        } else {
            [self.view endEditing:YES];
        }
        return NO;
    }
    return YES;
}

3
si (cellule == néant)
{
    cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellIdentifier];
    txt_Input = [[UITextField alloc] initWithFrame: CGRectMake (0, 10, 150, 30)];
    txt_Input.tag = indexPath.row + 1;
    [self.array_Textfields addObject: txt_Input]; // Initialise le tableau mutable dans ViewDidLoad
}

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

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt devenuFirstResponder];
    retourner OUI;
}

3

J'avais environ 10+ UITextField dans mon story-board et la façon dont j'ai activé la fonctionnalité suivante était de créer un tableau de UITextField et de faire du prochain UITextField le premier répondeur. Voici le fichier d'implémentation:

#import "RegistrationTableViewController.h"

@interface RegistrationTableViewController ()
@property (weak, nonatomic) IBOutlet UITextField *fullNameTextField;
@property (weak, nonatomic) IBOutlet UITextField *addressTextField;
@property (weak, nonatomic) IBOutlet UITextField *address2TextField;
@property (weak, nonatomic) IBOutlet UITextField *cityTextField;
@property (weak, nonatomic) IBOutlet UITextField *zipCodeTextField;
@property (weak, nonatomic) IBOutlet UITextField *urlTextField;
@property (weak, nonatomic) IBOutlet UITextField *usernameTextField;
@property (weak, nonatomic) IBOutlet UITextField *emailTextField;
@property (weak, nonatomic) IBOutlet UITextField *passwordTextField;
@property (weak, nonatomic) IBOutlet UITextField *confirmPWTextField;

@end
NSArray *uiTextFieldArray;
@implementation RegistrationTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"view did load");
    uiTextFieldArray = @[self.fullNameTextField,self.addressTextField,self.address2TextField,self.cityTextField,self.zipCodeTextField,self.urlTextField,self.usernameTextField,self.emailTextField,self.passwordTextField,self.confirmPWTextField];
    for(UITextField *myField in uiTextFieldArray){
        myField.delegate = self;
    }


}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
    long index = [uiTextFieldArray indexOfObject:textField];
    NSLog(@"%ld",index);
    if(index < (uiTextFieldArray.count - 1)){
        [uiTextFieldArray[++index] becomeFirstResponder];
    }else{
        [uiTextFieldArray[index] resignFirstResponder];
    }
    return YES;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

3

Cela a fonctionné pour moi dans Xamarin.iOS / Monotouch. Modifiez le bouton du clavier sur Suivant, passez le contrôle au UITextField suivant et masquez le clavier après le dernier UITextField.

private void SetShouldReturnDelegates(IEnumerable<UIView> subViewsToScout )
{
  foreach (var item in subViewsToScout.Where(item => item.GetType() == typeof (UITextField)))
  {
    (item as UITextField).ReturnKeyType = UIReturnKeyType.Next;
    (item as UITextField).ShouldReturn += (textField) =>
    {
        nint nextTag = textField.Tag + 1;
        var nextResponder = textField.Superview.ViewWithTag(nextTag);
        if (null != nextResponder)
            nextResponder.BecomeFirstResponder();
        else
            textField.Superview.EndEditing(true); 
            //You could also use textField.ResignFirstResponder(); 

        return false; // We do not want UITextField to insert line-breaks.
    };
  }
}

À l'intérieur du ViewDidLoad, vous aurez:

Si vos TextFields n'ont pas de balise, définissez-le maintenant:

txtField1.Tag = 0;
txtField2.Tag = 1;
txtField3.Tag = 2;
//...

et juste l'appel

SetShouldReturnDelegates(yourViewWithTxtFields.Subviews.ToList());
//If you are not sure of which view contains your fields you can also call it in a safer way:
SetShouldReturnDelegates(txtField1.Superview.Subviews.ToList());
//You can also reuse the same method with different containerViews in case your UITextField are under different views.

3

C'est une solution simple et rapide, sans utiliser de tag, pas de trucs de storyboard ...

Utilisez simplement cette extension:

extension UITextField{

    func nextTextFieldField() -> UITextField?{
        //field to return
        var returnField : UITextField?
        if self.superview != nil{
            //for each view in superview
            for (_, view) in self.superview!.subviews.enumerate(){
                //if subview is a text's field
                if view.isKindOfClass(UITextField){
                    //cast curent view as text field
                    let currentTextField = view as! UITextField
                    //if text field is after the current one
                    if currentTextField.frame.origin.y > self.frame.origin.y{
                        //if there is no text field to return already
                        if returnField == nil {
                            //set as default return
                            returnField = currentTextField
                        }
                            //else if this this less far than the other
                        else if currentTextField.frame.origin.y < returnField!.frame.origin.y{
                            //this is the field to return
                            returnField = currentTextField
                        }
                    }
                }
            }
        }
        //end of the mdethod
        return returnField
    }

}

Et appelez-le comme ceci (par exemple) avec votre délégué textfield:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    textField.nextTextFieldField()?.becomeFirstResponder()
    return true
}

Un commentaire à ce sujet est que votre méthode suppose que tous les champs sont empilés verticalement dans une seule vue d'ensemble. Il ne permet pas les champs groupés et ne fonctionne pas lorsque les champs de texte peuvent être côte à côte. Ce dernier est particulièrement important de nos jours lorsque l'on utilise des classes de taille pour réorganiser les formulaires pour les vues horizontales et lorsque les formulaires sont affichés sur des tablettes.
Michael Long

3

Une manière plus sûre et plus directe, en supposant:

  • les délégués de champ de texte sont définis sur votre contrôleur de vue
  • tous les champs de texte sont des sous-vues de la même vue
  • les champs de texte ont des balises dans l'ordre dans lequel vous souhaitez progresser (par exemple, textField2.tag = 2, textField3.tag = 3, etc.)
  • le passage au champ de texte suivant se produira lorsque vous appuyez sur le bouton de retour du clavier (vous pouvez le changer en suivant , terminé , etc.)
  • vous voulez que le clavier disparaisse après le dernier champ de texte

Swift 4.1:

extension ViewController: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        let nextTag = textField.tag + 1
        guard let nextTextField = textField.superview?.viewWithTag(nextTag) else {
            textField.resignFirstResponder()
            return false
        }

    nextTextField.becomeFirstResponder()

    return false

    }
}

2

dans textFieldShouldReturn, vous devez vérifier que le champ de texte sur lequel vous vous trouvez n'est pas le dernier lorsqu'il clique ensuite et si ce n'est pas le cas, ne fermez pas le clavier.


Hé, ok, cela fonctionne, mais comment puis-je passer au champ de texte suivant avec le bouton "Suivant"?
phx

2

Ceci est un ancien message, mais a un rang de page élevé, je vais donc carillon avec ma solution.

J'ai eu un problème similaire et j'ai fini par créer une sous-classe de UIToolbarpour gérer la fonctionnalité suivante / précédente / terminée dans une table dynamique avec des sections: https://github.com/jday001/DataEntryToolbar

Vous définissez la barre d'outils comme inputAccessoryView de vos champs de texte et les ajoutez à son dictionnaire. Cela vous permet de les parcourir en avant et en arrière, même avec du contenu dynamique. Il existe des méthodes déléguées si vous souhaitez déclencher votre propre fonctionnalité lorsque la navigation textField se produit, mais vous n'avez pas à gérer la gestion des balises ou l'état du premier répondant.

Il y a des extraits de code et un exemple d'application sur le lien GitHub pour vous aider avec les détails d'implémentation. Vous aurez besoin de votre propre modèle de données pour garder une trace des valeurs à l'intérieur des champs.


2

Sans utiliser de balises et sans ajouter de propriété pour nextField / nextTextField, vous pouvez essayer ceci pour émuler TAB, où "testInput" est votre champ actif actuel:

if ([textInput isFirstResponder])
    [textInput.superview.subviews enumerateObjectsAtIndexes:
     [NSIndexSet indexSetWithIndexesInRange:
      NSMakeRange([textInput.superview.subviews indexOfObject:textInput]+1,
                  [textInput.superview.subviews count]-[textInput.superview.subviews indexOfObject:textInput]-1)]
                                                    options:0 usingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
                                                        *stop = !obj.hidden && [obj becomeFirstResponder];
                                                    }];
if ([textInput isFirstResponder])
    [textInput.superview.subviews enumerateObjectsAtIndexes:
     [NSIndexSet indexSetWithIndexesInRange:
      NSMakeRange(0,
                  [textInput.superview.subviews indexOfObject:textInput])]
                                                    options:0 usingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
                                                        *stop = !obj.hidden && [obj becomeFirstResponder];
                                                    }];

2

J'utilise la réponse de Michael G. Emmons depuis environ un an maintenant, ça marche très bien. J'ai remarqué récemment que l'appel à resignFirstResponder puis à devientFirstResponder immédiatement peut provoquer un "pépin" du clavier, disparaissant puis apparaissant immédiatement. J'ai légèrement changé sa version pour ignorer le resignFirstResponder si le nextField est disponible.

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

    if ([textField isKindOfClass: [NRTextField class]]))
    {
        NRTextField * nText = (NRTextField *) textField;
        if ([nText nextField]! = nil) {
            dispatch_async (dispatch_get_main_queue (),
                           ^ {[[nText nextField] devientFirstResponder]; });

        }
        autre{
            [textField resignFirstResponder];
        }
    }
    autre{
        [textField resignFirstResponder];
    }

    return true;

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.