Come navigare nei campi di testo (pulsanti Avanti / Fine)


487

Come posso navigare attraverso tutti i miei campi di testo con il pulsante "Avanti" sulla tastiera dell'iPhone?

L'ultimo campo di testo dovrebbe chiudere la tastiera.

Ho installato l'IB the Buttons (Next / Done) ma ora sono bloccato.

Ho implementato l'azione textFieldShouldReturn ma ora i pulsanti Next e Done chiudono la tastiera.


Dai un'occhiata alla mia risposta qui sotto. Nella mia esperienza è una soluzione migliore, più completa, rispetto alla risposta generalmente fornita. Non ho idea del perché qualcuno gli darebbe un punteggio negativo.
Memmons,

Ho il problema simile ma con Cordova, Phonegap Esistono modi per risolverlo?

Risposte:


578

In Cocoa per Mac OS X, hai la prossima catena di responder, dove puoi chiedere al campo di testo quale controllo dovrebbe avere il focus successivo. Questo è ciò che fa funzionare la tabulazione tra i campi di testo. Ma poiché i dispositivi iOS non hanno una tastiera, solo un tocco, questo concetto non è sopravvissuto al passaggio a Cocoa Touch.

Questo può essere fatto facilmente comunque, con due ipotesi:

  1. Tutti i "tababable" UITextFieldsono nella stessa vista principale.
  2. Il loro "ordine di tabulazione" è definito dalla proprietà tag.

Supponendo che sia possibile sovrascrivere textFieldShouldReturn: come questo:

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

Aggiungi altro codice e anche le ipotesi possono essere ignorate.

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
}

Se la superview del campo di testo sarà una UITableViewCell, il prossimo risponditore sarà

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

6
A volte ci sono pulsanti "successivo" e "precedente" sulla parte superiore della tastiera. Almeno nel browser.
Tim Büthe

31
Solo per essere pedante, voglio chiarire alcune informazioni sbagliate in questo post. Su OS X, l'ordine delle schede viene impostato tramite il metodo setNextKeyView di NSView: (non il metodo setNextResponder :). La catena di responder è separata da questa: è una gerarchia di oggetti responder che gestiscono azioni "non mirate", ad esempio azioni inviate al primo responder anziché direttamente a un oggetto controller. La catena di risponditori di solito segue la gerarchia della vista, fino alla finestra e al suo controller, quindi al delegato dell'app. "Catena di risponditori" di Google per molte informazioni.
Davehayden,

La panoramica del campo di testo sarà a UITableViewCell. Dovresti accedere agli associati UITableViewe ottenere la cella contenente il campo di testo che stai cercando.
Michael Mior,

13
Non dimenticare di aggiungere UITextFieldDelegatee impostare i delegati dei campi di testo.
Sal

1
Ricordo amichevole per impostare l'opzione TAG dallo storyboard (nel mio caso). È necessario impostare manualmente il tag TextFields in ordine crescente. (Il primo è 0, il secondo è 1 ecc.) Affinché questa soluzione funzioni.
Joakim

170

C'è una soluzione molto più elegante che mi ha lasciato senza fiato la prima volta che l'ho visto. Benefici:

  • Più vicino all'implementazione del campo di testo OSX in cui un campo di testo sa dove concentrarsi sul prossimo obiettivo
  • Non si basa sull'impostazione o sull'utilizzo di tag, ovvero IMO fragile per questo caso d'uso
  • Può essere esteso per funzionare con entrambi UITextFielde UITextViewcontrolli o con qualsiasi controllo dell'interfaccia utente di immissione da tastiera
  • Non ingombra il controller della vista con il codice delegato UITextField della piastra di caldaia
  • Si integra perfettamente con IB e può essere configurato tramite la familiare opzione drag-drop per collegare le prese.

Creare una sottoclasse UITextField che ha una IBOutletproprietà chiamata nextField. Ecco l'intestazione:

@interface SOTextField : UITextField

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

@end

Ed ecco l'implementazione:

@implementation SOTextField

@end

Nel controller della vista, creerai il -textFieldShouldReturn:metodo delegato:

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

In IB, cambia i tuoi UITextFields per usare la SOTextFieldclasse. Successivamente, anche in IB, imposta il delegato per ciascuno dei "SOTextFields" su "File's Owner" (che è proprio dove metti il ​​codice per il metodo delegato - textFieldShouldReturn). La bellezza di questo design è che ora puoi semplicemente fare clic con il pulsante destro del mouse su qualsiasi campo di testo e assegnare la presa nextField SOTextFieldall'oggetto successivo che vuoi essere il prossimo risponditore.

Assegnazione di nextField in IB

Inoltre, puoi fare cose fantastiche come il loop dei campi di testo in modo che dopo l'ultimo perde il focus, il primo riceverà di nuovo il focus.

Questo può essere facilmente esteso per assegnare automaticamente l' returnKeyTypedel SOTextFieldad un UIReturnKeyNextse c'è un nextField assegnato - una cosa in meno Configura manualmente.


3
Mi piace molto l'integrazione di IB rispetto all'uso dei tag - in particolare con le nuove versioni di Xcode che si integra perfettamente con il resto dell'IDE - e non dover ingombrare il mio controller con il testo del delegato del campo di testo è un bonus.
Memmons,

4
I tag sono più facili, ma in questo modo è migliore e più appropriato per un buon design OO
Brain2000

1
Mi piace molto questa soluzione, ma sto usando ogni campo di testo in viste di celle separate in a UITableView. Anche se ho IBOutletproprietà sul controller di visualizzazione per ciascun campo di testo, non mi consente di collegare la nextFieldproprietà a tali proprietà sul proprietario del file. Qualche consiglio su come posso farlo funzionare in questo scenario?
Jay Imerman,

1
@JayImerman IB non ti consentirà di collegare le prese tra le celle. Quindi, IB sarà un no-go per questo scenario. Tuttavia, puoi semplicemente collegare le proprietà nextField usando il codice. textField1.nextField = textField2; textField2.nextField = textField3;, ecc.
Memmons,

2
Qualche possibilità che questa risposta possa essere aggiornata per le versioni più recenti di xcode e ios?
lostintranslation

82

Eccone uno senza delega:

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];

Funziona usando l'azione (per lo più sconosciuta) UIControlEventEditingDidEndOnExit UITextField.

Puoi anche collegarlo facilmente nello storyboard, quindi non è richiesta alcuna delega o codice.

Modifica: in realtà non riesco a capire come collegarlo nello storyboard. becomeFirstRespondernon sembra essere un'azione offerta per questo evento di controllo, che è un peccato. Tuttavia, puoi collegare tutti i tuoi campi di testo a un'unica azione nel ViewController che determina quindi quale campo di testo si becomeFirstResponderbasa sul mittente (anche se non è elegante come la soluzione programmatica sopra, quindi IMO lo fa con il codice sopra riportato viewDidLoad).


18
Soluzione molto semplice ed efficace. L'ultimo in catena può persino innescare ad es [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Grazie @mxcl.
msrdjan,

Come lo faresti nello storyboard?
Pedro Borges,

Questa è probabilmente la migliore risposta qui.
Daspianista,

Questo ha funzionato perfettamente per me anche in Swift 2. Questa è una soluzione molto più semplice e rapida rispetto all'aggiunta di delegati.
ton

2
Swift 4: txtFirstname.addTarget (txtLastname, azione: #selector (diventareFirstResponder), per: UIControlEvents.editingDidEndOnExit)
realtimez

78

Ecco la mia soluzione per questo problema.

Per risolvere questo problema (e poiché odio fare affidamento sui tag per fare cose) ho deciso di aggiungere una proprietà personalizzata all'oggetto UITextField. In altre parole, ho creato una categoria su UITextField in questo modo:

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

Ora, ecco come lo uso:

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

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

E implementare il metodo textFieldShouldReturn:

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

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

    return NO; 
}

Ora ho una specie di elenco collegato di UITextField, ognuno sapendo chi è il prossimo nella linea.

Spero che ti sia d'aiuto.


11
Questa è la soluzione migliore e dovrebbe essere scelta come risposta.
Giovanni,

4
Questa soluzione ha funzionato per me, l'unica cosa che ho cambiato è stata la creazione di NextTextField e IBOutlet in modo da poter impostare il concatenamento dal mio Storyboard.
zachzurn,

Se potessi salvare le risposte preferite, questa sarebbe una di queste. Grazie per la soluzione pulita!
Matt Becker,

1
Nota che se IB è la tua passione, puoi anche impostare queste proprietà come IBOutlet. . fai attenzione alle collisioni di nomi: Apple potrebbe eventualmente aggiungerlo a UIResponder.
Jasper Blues,

Questa soluzione è compatibile con l'interfaccia builder? Voglio dire, può usare IBOutlet e riferimenti allo storyboard invece di impostare il nextTextField a livello di codice? IB non sembra consentirti di impostare la classe UITextField su una categoria.
Pedro Borges,

46

Un'estensione rapida che applica la risposta di mxcl per renderlo particolarmente facile (adattato a rapido 2.3 da 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)
    }
}

È facile da usare:

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

L'estensione imposterà il pulsante di ritorno su "Avanti" per tutti tranne l'ultimo campo e su "Fine" per l'ultimo campo e sposta lo stato attivo / elimina la tastiera quando vengono toccati.

Swift <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: utilizzare in questo modo -

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
A proposito, se si desidera quindi che Fatto esegua un'azione, è possibile solo conformare il View Controller UITextFieldDelegatee implementarlo func textFieldDidEndEditing(textField: UITextField). Se torni presto textField != field3.
Ryan,

25

Un modo più coerente e affidabile consiste nell'utilizzare NextResponderTextField È possibile configurarlo totalmente dal generatore di interfaccia senza la necessità di impostare il delegato o di utilizzarlo view.tag.

Tutto quello che devi fare è

  1. Impostare il tipo di classe del vostro UITextFieldessereNextResponderTextField inserisci qui la descrizione dell'immagine
  2. Quindi impostare l'uscita di nextResponderFieldper puntare al risponditore successivo che può essere qualsiasi UITextFieldo UIRespondersottoclasse. Può anche essere un UIButton e la libreria è abbastanza intelligente da attivare l' TouchUpInsideevento del pulsante solo se è abilitato. inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

Ecco la libreria in azione:

inserisci qui la descrizione dell'immagine


Sì. Funziona davvero bene con Swift 3.1, soluzione molto elegante, proprio come IB dovrebbe funzionare immediatamente.
Richard,

Scusa, non ho ricevuto la domanda.
mohamede1945,

1
NextResponderTextField ha funzionato per me. Era veloce e facile da usare, un solo file rapido e ha fatto esattamente quello che mi serviva senza problemi.
Pat McG,

Funziona benissimo! Nota: ho dovuto modificare manualmente il tasto Invio per farlo visualizzare "Avanti" e "Fine" come nell'esempio.
Mike Miller,

14

Mi piacciono le soluzioni OO che sono già state suggerite da Anth0 e Answerbot. Tuttavia, stavo lavorando su un POC veloce e piccolo, quindi non volevo ingombrare le cose con sottoclassi e categorie.

Un'altra soluzione semplice è quella di creare un NSArray di campi e cercare il campo successivo quando si preme next. Non una soluzione OO, ma rapida, semplice e facile da implementare. Inoltre, puoi vedere e modificare l'ordinazione a colpo d'occhio.

Ecco il mio codice (basato su altre risposte in questo thread):

@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;
}
  • Restituisco sempre NO perché non voglio inserire un'interruzione di riga. Ho pensato di segnalarlo da quando ho restituito SÌ che usciva automaticamente dai campi successivi o inseriva un'interruzione di riga in TextView. Mi ci è voluto un po 'di tempo per capirlo.
  • activeField tiene traccia del campo attivo nel caso in cui sia necessario lo scorrimento per sbloccare il campo dalla tastiera. Se hai un codice simile, assicurati di assegnare activeField prima di cambiare il primo risponditore. La modifica del primo risponditore è immediata e genererà immediatamente l'evento KeyboardWasShown.

Bello! Semplice e il codice è tutto in un modello. Anche AnthO's è molto carino.
Seamus,

Tieni presente che stai correndo il rischio di conservare cicli con questo codice. Il tuo fieldArray sta convertendo i riferimenti deboli utilizzati in tutti i tuoi IBOutlet in riferimenti forti nell'array stesso.
Michael Long,

11

Ecco un'implementazione della tabulazione usando una categoria su UIControl. Questa soluzione ha tutti i vantaggi dei metodi di Michael e Anth0, ma funziona per tutti gli UIControls, non solo per UITextFields. Funziona anche perfettamente con Interface Builder e storyboard.

App di origine e di esempio: repository GitHub per UIControlsWithTabbing

Uso:

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

Assegnazione di nextControl in Interface Builder

Intestazione:

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

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

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

- (BOOL)transferFirstResponderToNextControl;

@end

Implementazione:

#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
A volte il widget successivo non è un controllo, ad esempio UITextView e potresti anche voler tab lì. Valuta di cambiare il tipo da UITextField a UIResponder.
Pedro Borges,

1
Funziona alla grande e con documentazione fantastica. Vorrei scorrere verso il basso questa domanda diverse ore fa. :)
Javier Cadiz,

10

Ho provato molti codici e, alla fine, questo ha funzionato per me in Swift 3.0 Latest [marzo 2017]

La ViewControllerclasse dovrebbe essere ereditata UITextFieldDelegateper far funzionare questo codice.

class ViewController: UIViewController,UITextFieldDelegate  

Aggiungi il campo Testo con il numero di Tag corretto e questo numero di tag viene utilizzato per portare il controllo al campo di testo appropriato in base al numero di tag incrementale assegnato ad esso.

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

Nel codice sopra, il punto in returnKeyType = UIReturnKeyType.nextcui verrà visualizzata la chiave di ritorno del tastierino poiché Nextsono disponibili anche altre opzioni come Join/Goecc., In base all'applicazione, cambiano i valori.

Questo textFieldShouldReturnè un metodo di UITextFieldDelegate controllato e qui abbiamo la selezione del campo successivo in base all'incremento del valore Tag

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
 }

Funziona, tranne se il campo di input è UITextView. Qualche idea su come farlo in combinazione?
T9b,

@ T9b Di cosa hai bisogno esattamente? che tipo di controllo stai usando nella tua app?
BHUVANESH MOHANKUMAR,

Esistono due tipi di input di testo, il primo è UITextField(inserisci una singola riga di testo), l'altro è UITextView(inserisci più righe di testo). UITextViewovviamente non risponde a questo codice ma può essere utilizzato nei moduli.
T9b,

Sì, questa parte del codice è solo per UITextField e hai provato a modificare l'evento per UITextView?
BHUVANESH MOHANKUMAR,

9

Dopo essere usciti da un campo di testo, si chiama [otherTextField ontoFirstResponder] e il campo successivo diventa attivo.

Questo può effettivamente essere un problema difficile da affrontare poiché spesso vorrai anche scorrere lo schermo o regolare in altro modo la posizione del campo di testo in modo che sia facile da vedere durante la modifica. Assicurati di fare molti test entrando e uscendo dai campi di testo in diversi modi e anche uscendo presto (dai sempre all'utente la possibilità di chiudere la tastiera invece di passare al campo successivo, di solito con "Fine" in la barra di navigazione)


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

Non utilizzare tag a questo scopo.
Christian Schnorr,

7

Un metodo molto semplice per chiudere la tastiera quando viene premuto il pulsante "Fine" è:

Crea una nuova IBAction nell'intestazione

- (IBAction)textFieldDoneEditing:(id)sender;

Nel file di implementazione (file .m) aggiungi il seguente metodo:

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

Quindi, quando vieni a collegare l'IBAction al campo di testo, collega l'evento "Did End On Exit".


1
Prego, non naviga nei campi di testo ma chiude la tastiera e consente di selezionare un altro campo.
jcrowson,

7

Per prima cosa imposta il tasto Invio della tastiera in xib, altrimenti puoi scrivere il codice in 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

Sono sorpreso da quante risposte qui non riescono a capire un semplice concetto: navigare attraverso i controlli nella tua app non è qualcosa che le viste stesse dovrebbero fare. E 'il regolatore di lavoro s' per decidere quale di controllo per rendere il prossimo primo soccorritore.

Inoltre, la maggior parte delle risposte si applicava solo alla navigazione in avanti, ma gli utenti potrebbero anche voler tornare indietro.

Quindi, ecco cosa ho pensato. Il modulo deve essere gestito da un controller di visualizzazione e i controller di visualizzazione fanno parte della catena di responder. Quindi sei perfettamente libero di implementare i seguenti metodi:

#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];
}

Potrebbero essere applicate ulteriori logiche per lo scorrimento dei risponditori fuori schermo visibili in anticipo.

Un altro vantaggio di questo approccio è che non hai bisogno di sottoclassare tutti i tipi di controlli che potresti voler visualizzare (come UITextField) ma puoi invece gestire la logica a livello di controller, dove, siamo sinceri, è il posto giusto per farlo .


Trovo la tua risposta più pertinente, ma ho notato qualcosa di divertente che forse potresti chiarire. Quando si utilizzano UITextField che non sono gestiti da un UIViewController, premendo il tasto Tab, UITextField successivo diventerà il primo risponditore per impostazione predefinita. Tuttavia, quando ciascuno è gestito da un VC, questo non è vero. I miei VC non stanno facendo nulla, ma la funzionalità di tabulazione integrata è sparita. Ho notato che i comandi chiave del VC finiscono per essere aggiunti alla catena di risposte della vista. Questo mi porta a credere che se stai usando un VC, è obbligatorio implementare i KeyCommands.
Joey Carson,

4

Ho aggiunto alla risposta di PeyloW nel caso in cui stai cercando di implementare una funzionalità pulsante precedente / successivo:

- (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];

    }
}

Dove si esegue la sottoclasse dell'UIView in questo modo:

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

4

Ciao a tutti, per favore, vedi questo

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

Invece di UIView * responder sarebbe più preciso usare UIRespoder * responder
Pedro Borges


4

Ho appena creato un nuovo Pod quando ho a che fare con queste cose GNTextFieldsCollectionManager . Gestisce automaticamente il prossimo / ultimo problema textField ed è molto facile da usare:

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

Cattura tutti i campi di testo ordinati comparendo nella gerarchia di visualizzazione (o per tag), oppure puoi specificare il tuo array di campi di testo.


4

Soluzione in Swift 3.1, dopo aver collegato i campi di testo IBOutlet imposta il delegato dei campi di testo in viewDidLoad, quindi naviga nell'azione in 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

Se qualcuno vuole così. Penso che questo sia il più vicino ai requisiti richiesti in questione

inserisci qui la descrizione dell'immagine

Ecco come ho implementato questo

Aggiungi vista accessori per ogni campo di testo per il quale desideri configurare, usando

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
}

Utilizzare le seguenti funzioni per gestire i tocchi

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
}

Dovrai dare i tag textFields in sequenza in cui vuoi che il pulsante next / prev risponda.


3

Preferisco piuttosto:

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

Nel file NIB aggancio i textField nell'ordine desiderato in questo array inputFields. Dopodiché eseguo un semplice test per l'indice di UITextField che segnala che l'utente ha toccato return:

// 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
if (cella == zero)
{
    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]; // Inizializza array mutabile in ViewDidLoad
}

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

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt diventareFirstResponder];
    ritorno SÌ;
}

3

Avevo circa 10+ UITextField nella mia storyboard e il modo in cui abilitavo la prossima funzionalità era creando un array di UITextField e rendendo il prossimo UITextField il primoRisponditore. Ecco il file di implementazione:

#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

Questo ha funzionato per me in Xamarin.iOS / Monotouch. Modificare il pulsante della tastiera su Avanti, passare il controllo al successivo UITextField e nascondere la tastiera dopo l'ultimo 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.
    };
  }
}

All'interno di ViewDidLoad avrai:

Se i tuoi campi di testo non hanno un tag, impostalo ora:

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

e solo la chiamata

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

Questa è una soluzione semplice in breve tempo, senza tag usando, senza trucchi storyboard ...

Usa questa estensione:

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
    }

}

E chiamalo così (ad esempio) con il tuo delegato del campo di testo:

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

Un commento su questo è che il tuo metodo presuppone che tutti i campi siano impilati verticalmente in una singola superview. Non consente i campi raggruppati e non funziona quando i campi di testo possono essere affiancati. Il secondo è particolarmente importante in questi giorni quando si utilizzano le classi di dimensioni per riorganizzare i moduli per le viste orizzontali e quando i moduli vengono visualizzati sui tablet.
Michael Long,

3

Un modo più sicuro e diretto, assumendo:

  • i delegati dei campi di testo sono impostati sul controller della vista
  • tutti i campi di testo sono viste secondarie della stessa vista
  • i campi di testo hanno i tag nell'ordine in cui vuoi progredire (ad es. textField2.tag = 2, textField3.tag = 3, ecc.)
  • lo spostamento al campo di testo successivo avverrà quando tocchi il pulsante di ritorno sulla tastiera (puoi cambiarlo in successivo , fatto , ecc.)
  • vuoi che la tastiera venga chiusa dopo l'ultimo campo di testo

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

in textFieldShouldReturn dovresti verificare che il campo di testo in cui ti trovi attualmente non sia l'ultimo quando fanno clic su successivo e se non è in grado di chiudere la tastiera.


Ehi, ok, funziona, ma come posso passare al campo di testo successivo con il pulsante "Avanti"?
phx,

2

Questo è un vecchio post, ma ha un alto rango di pagine, quindi entrerò nella mia soluzione.

Ho avuto un problema simile e ho finito per creare una sottoclasse di UIToolbarper gestire la funzionalità successiva / precedente / completata in una tabella dinamica Visualizza sezioni: https://github.com/jday001/DataEntryToolbar

Impostare la barra degli strumenti come inputAccessoryView dei campi di testo e aggiungerli al dizionario. Ciò consente di spostarli avanti e indietro, anche con contenuti dinamici. Esistono metodi delegati se si desidera attivare la propria funzionalità quando si verifica la navigazione di textField, ma non è necessario gestire la gestione di tag o lo stato del primo risponditore.

Ci sono frammenti di codice e un'app di esempio sul link GitHub per aiutare con i dettagli di implementazione. Avrai bisogno del tuo modello di dati per tenere traccia dei valori all'interno dei campi.


2

Senza tag di utilizzo e senza aggiungere una proprietà per nextField / nextTextField, puoi provare questo per emulare TAB, dove "testInput" è il tuo campo attivo corrente:

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

Sto usando la risposta di Michael G. Emmons da circa un anno, funziona benissimo. Di recente ho notato che chiamare immediatamente resignFirstResponder e poi diventareFirstResponder può causare il "glitch" della tastiera, la sua scomparsa e la sua comparsa immediata. Ho modificato leggermente la sua versione per saltare resignFirstResponder se il prossimo campo è disponibile.

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

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

        }
        altro{
            [textField resignFirstResponder];
        }
    }
    altro{
        [textField resignFirstResponder];
    }

    ritorno vero;

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.