Cómo navegar por los campos de texto (botones Siguiente / Listo)

487

¿Cómo puedo navegar por todos mis campos de texto con el botón "Siguiente" en el teclado del iPhone?

El último campo de texto debe cerrar el teclado.

He configurado los botones IB (Siguiente / Listo) pero ahora estoy atascado.

Implementé la acción textFieldShouldReturn pero ahora los botones Siguiente y Listo cierran el teclado.

phx
fuente
Por favor, eche un vistazo a mi respuesta a continuación. En mi experiencia, es una solución mejor y más completa que la respuesta que se suele dar. No tengo idea de por qué alguien le daría una calificación negativa.
memmons
Tengo el problema similar pero con Cordova, Phonegap ¿Hay alguna forma de resolverlo?

Respuestas:

578

En Cocoa para Mac OS X, tiene la siguiente cadena de respuesta, donde puede preguntar al campo de texto en qué control debe centrarse a continuación. Esto es lo que hace que el tabulado entre campos de texto funcione. Pero dado que los dispositivos iOS no tienen teclado, solo toquen, este concepto no ha sobrevivido a la transición a Cocoa Touch.

Esto puede hacerse fácilmente de todos modos, con dos supuestos:

  1. Todos los "tabulables" UITextFieldestán en la misma vista principal.
  2. Su "orden de tabulación" está definido por la propiedad de etiqueta.

Suponiendo esto, puede anular textFieldShouldReturn: como esto:

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

Agregue un poco más de código, y los supuestos también pueden ignorarse.

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 vista general del campo de texto será un UITableViewCell, el siguiente respondedor será

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!
PeyloW
fuente
66
A veces hay botones "siguiente" y "anterior" en la parte superior del teclado. Al menos en el navegador.
Tim Büthe
31
Solo para ser pedante, quiero aclarar alguna información errónea en esta publicación. En OS X, el orden de tabulación se configura mediante el método setNextKeyView de NSView: (no setNextResponder :). La cadena de respuesta está separada de esto: es una jerarquía de objetos de respuesta que manejan acciones "no dirigidas", por ejemplo, acciones enviadas al primer respondedor en lugar de directamente a un objeto controlador. La cadena de respuesta generalmente sigue la jerarquía de vista, hasta la ventana y su controlador, y luego el delegado de la aplicación. Google "cadena de respuesta" para mucha información.
davehayden
La supervista del campo de texto será a UITableViewCell. Debería acceder a la asociada UITableViewy obtener la celda que contiene el campo de texto que está buscando.
Michael Mior
13
No olvide agregar UITextFieldDelegatey establecer los delegados de campos de texto.
Sal
1
Recordatorio amistoso para configurar la opción TAG desde el guión gráfico (en mi caso). Debe configurar manualmente la etiqueta TextFields en orden ascendente. (Primero es 0, segundo es 1, etc.) para que esta solución funcione.
Joakim
170

Hay una solución mucho más elegante que me sorprendió la primera vez que la vi. Beneficios:

  • Más cerca de la implementación del campo de texto OSX, donde un campo de texto sabe a dónde debe ir el foco.
  • No se basa en la configuración o el uso de etiquetas, que son IMO frágiles para este caso de uso
  • Se puede extender para trabajar con ambos UITextFieldyUITextView controles - o cualquier entrada de control de la interfaz de usuario del teclado
  • No abarrota su controlador de vista con código de delegado UITextField repetitivo
  • Se integra muy bien con IB y se puede configurar a través de la opción familiar de arrastrar y soltar para conectar salidas.

Cree una subclase UITextField que tenga una IBOutletpropiedad llamada nextField. Aquí está el encabezado:

@interface SOTextField : UITextField

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

@end

Y aquí está la implementación:

@implementation SOTextField

@end

En su controlador de vista, creará el -textFieldShouldReturn:método delegado:

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

En IB, cambie sus UITextFields para usar la SOTextFieldclase. A continuación, también en IB, configure el delegado para cada uno de los 'SOTextFields' al 'Propietario del archivo' (que es justo donde coloca el código para el método de delegado: textFieldShouldReturn). La belleza de este diseño es que ahora puede simplemente hacer clic derecho en cualquier campo de texto y asignar la salida nextField al siguiente SOTextFieldobjeto que desea que sea el próximo respondedor.

Asignación de nextField en IB

Además, puede hacer cosas geniales como recorrer los campos de texto para que después de que el último pierda el foco, el primero reciba el foco nuevamente.

Esto se puede extender fácilmente para asignar automáticamente el returnKeyTypede SOTextFielda UIReturnKeyNextsi hay un nextField asignado, una cosa menos se configura manualmente.

memmons
fuente
3
Realmente me gusta la integración de IB sobre el uso de etiquetas, particularmente con las versiones más nuevas de Xcode que integran bien a IB con el resto del IDE, y no tener que saturar mi controlador con el código de delegado textField de caldera es una ventaja.
memmons
44
Las etiquetas son más fáciles, pero de esta manera es mejor y más apropiado para un buen diseño de OO
Brain2000
1
Realmente me gusta esta solución, pero estoy usando cada campo de texto en vistas de celda separadas en a UITableView. Aunque tengo IBOutletpropiedades en el controlador de vista para cada campo de texto, no me permite vincular la nextFieldpropiedad a esas propiedades en el Propietario del archivo. ¿Algún consejo sobre cómo puedo hacer que funcione en este escenario?
Jay Imerman
1
@JayImerman IB no le permitirá conectar salidas entre celdas. Entonces, IB será un no-go para este escenario. Sin embargo, puede conectar las propiedades nextField usando el código. textField1.nextField = textField2; textField2.nextField = textField3;, etc.
memmons
2
¿Hay alguna posibilidad de que esta respuesta se pueda actualizar para las versiones más nuevas de xcode e ios?
lostintranslation
82

Aquí hay uno sin delegación:

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

Funciona utilizando la UIControlEventEditingDidEndOnExit UITextFieldacción (en su mayoría desconocida) .

También puede conectarlo fácilmente en el guión gráfico, por lo que no se requiere delegación ni código.

Editar: en realidad no puedo encontrar la manera de conectar esto en el guión gráfico. becomeFirstResponderno parece ser una acción ofrecida para este evento de control, lo cual es una pena. Aún así, puede conectar todos sus campos de texto a una sola acción en su ViewController que luego determina a qué TextField se becomeFirstResponderbasa en el remitente (aunque entonces no es tan elegante como la solución programática anterior, así que IMO lo haga con el código anterior viewDidLoad).

mxcl
fuente
18
Solución muy simple y efectiva. El último en la cadena puede incluso desencadenar, por ejemplo [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Gracias @mxcl.
msrdjan
¿Cómo harías esto en storyboard?
Pedro Borges
Esta es probablemente la mejor respuesta aquí.
daspianist
Esto funcionó perfectamente para mí incluso en Swift 2. Esta es una solución mucho más simple y rápida que agregar delegados.
ton
2
Swift 4: txtFirstname.addTarget (txtLastname, acción: #selector (BecomeFirstResponder), para: UIControlEvents.editingDidEndOnExit)
realtimez el
78

Aquí está mi solución para este problema.

Para resolver esto (y porque odio depender de las etiquetas para hacer cosas) decidí agregar una propiedad personalizada al objeto UITextField. En otras palabras, creé una categoría en UITextField como esta:

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

Ahora, así es como 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 implemente el método textFieldShouldReturn:

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

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

    return NO; 
}

Ahora tengo una especie de lista vinculada de UITextField, cada uno sabe quién es el próximo en la línea.

Espero que ayude.

Anth0
fuente
11
Esta es la mejor solución y debe elegirse como respuesta.
Giovanni
44
Esta solución funcionó para mí, lo único que cambié fue crear el siguiente TextField e IBOutlet para poder configurar el encadenamiento desde mi Storyboard.
zachzurn
Si pudiera guardar las respuestas favoritas, esta sería una de ellas. ¡Gracias por la solución limpia!
Matt Becker
1
Tenga en cuenta que si IB es lo suyo, también puede establecer estas propiedades como IBOutlets. . tenga cuidado con las colisiones de nombres: Apple eventualmente puede agregar esto a UIResponder.
Jasper Blues
¿Es esta solución compatible con el generador de interfaces? Quiero decir, ¿puede usar IBOutlets y referencias de guiones gráficos en lugar de configurar programáticamente nextTextField? IB no parece permitirle establecer la clase UITextField en una categoría.
Pedro Borges
46

Una extensión rápida que aplica la respuesta de mxcl para hacer esto particularmente fácil (adaptado a swift 2.3 por 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)
    }
}

Es fácil de usar:

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

La extensión establecerá el botón de retorno en "Siguiente" para todos excepto el último campo y en "Listo" para el último campo, y cambiará el enfoque / descartará el teclado cuando se toquen.

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: use así:

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)
        }
    }
Amos Joshua
fuente
55
Por cierto, si desea que Done realice una acción, puede hacer que su controlador de vista se ajuste UITextFieldDelegatee implemente func textFieldDidEndEditing(textField: UITextField). Solo vuelve temprano si textField != field3.
Ryan
25

Una forma más consistente y robusta es usar NextResponderTextField . Puede configurarlo totalmente desde el generador de interfaces sin necesidad de configurar el delegado o usarlo view.tag.

Todo lo que necesitas hacer es

  1. Establezca el tipo de clase de su UITextFieldserNextResponderTextField ingrese la descripción de la imagen aquí
  2. Luego configure la salida de la nextResponderFieldpara que apunte al siguiente respondedor, puede ser cualquier cosa UITextFieldo cualquier UIRespondersubclase. También puede ser un UIButton y la biblioteca es lo suficientemente inteligente como para activar el TouchUpInsideevento del botón solo si está habilitado. ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Aquí está la biblioteca en acción:

ingrese la descripción de la imagen aquí

mohamede1945
fuente
Si. Funciona muy bien con Swift 3.1, una solución muy elegante, tal como IB debería estar funcionando desde el primer momento.
Richard
Lo siento, no recibí la pregunta.
mohamede1945
1
NextResponderTextField funcionó para mí. Fue rápido y fácil de usar, solo un archivo rápido, e hizo exactamente lo que necesitaba sin problemas.
Pat McG
¡Esto funciona muy bien! Nota: Tuve que cambiar la tecla Retorno manualmente para que mostrara "Siguiente" y "Listo" como en el ejemplo.
Mike Miller
14

Me gustan las soluciones OO que ya han sido sugeridas por Anth0 y Answerbot. Sin embargo, estaba trabajando en un POC rápido y pequeño, por lo que no quería saturar las cosas con subclases y categorías.

Otra solución simple es crear una matriz NSA de campos y buscar el siguiente campo cuando presiona Siguiente. No es una solución OO, pero es rápida, simple y fácil de implementar. Además, puede ver y modificar el pedido de un vistazo.

Aquí está mi código (construido sobre otras respuestas en este hilo):

@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;
}
  • Siempre devuelvo NO porque no quiero insertar un salto de línea. Solo pensé en señalarlo, ya que cuando regresé SÍ saldría automáticamente de los campos siguientes o insertaría un salto de línea en mi TextView. Me tomó un poco de tiempo darme cuenta.
  • activeField realiza un seguimiento del campo activo en caso de que sea necesario desplazarse para desbloquear el campo del teclado. Si tiene un código similar, asegúrese de asignar activeField antes de cambiar el primer respondedor. Cambiar el primer respondedor es inmediato y activará el evento KeyboardWasShown inmediatamente.
Marquesina
fuente
¡Agradable! Simple y el código es todo en un modelo. AnthO's también es muy agradable.
Seamus
Tenga en cuenta que corre el riesgo de retener ciclos con este código. Su fieldArray está convirtiendo las referencias débiles utilizadas en todos sus IBOutlets en referencias fuertes en la matriz misma.
Michael Long
11

Aquí hay una implementación de tabulación usando una categoría en UIControl. Esta solución tiene todas las ventajas de los métodos de Michael y Anth0, pero funciona para todos los controles UIC, no solo para UITextFields. También funciona a la perfección con Interface Builder y storyboards.

Fuente y aplicación de muestra: repositorio de GitHub para UIControlsWithTabbing

Uso:

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

Asignación de nextControl en Interface Builder

Encabezamiento:

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

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

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

- (BOOL)transferFirstResponderToNextControl;

@end

Implementación:

#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
picciano
fuente
1
A veces, el siguiente widget no es un control, es decir, UITextView y es posible que desee tabular allí también. Considere cambiar el tipo de UITextField a UIResponder.
Pedro Borges
1
Funciona muy bien y con documentación fantástica. Deseo que me desplace hacia abajo a esta pregunta hace varias horas. :)
Javier Cadiz
10

He probado muchos códigos y finalmente, esto funcionó para mí en Swift 3.0 Latest [marzo de 2017]

La ViewControllerclase debe heredarse UITextFieldDelegatepara que este código funcione.

class ViewController: UIViewController,UITextFieldDelegate  

Agregue el campo de texto con el número de etiqueta adecuado y este número de etiqueta se utiliza para llevar el control al campo de texto apropiado en función del número de etiqueta incremental asignado a él.

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

En el código anterior, el returnKeyType = UIReturnKeyType.nextlugar hará que la tecla de retorno del teclado numérico se muestre, ya Nextque también tiene otras opciones, Join/Goetc., según su aplicación, cambie los valores.

Este textFieldShouldReturnes un método controlado por UITextFieldDelegate y aquí tenemos la siguiente selección de campo basada en el incremento del valor de la etiqueta

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
 }
BHUVANESH MOHANKUMAR
fuente
Esto funciona, excepto si el campo de entrada es UITextView. ¿Alguna idea de cómo hacer esto en combinación?
T9b
@ T9b ¿Qué necesitas exactamente? ¿Qué tipo de control estás usando en tu aplicación?
BHUVANESH MOHANKUMAR
Hay dos tipos de entradas de texto, la primera es UITextField(ingrese una sola línea de texto), la otra es UITextView(ingrese varias líneas de texto). UITextViewobviamente no responde a este código pero puede usarse en formularios.
T9b
Sí, esta parte del código solo para UITextField y ¿ha intentado modificar el evento para UITextView?
BHUVANESH MOHANKUMAR
9

Después de salir de un campo de texto, llama a [otherTextField BecomeFirstResponder] y el siguiente campo se enfoca.

En realidad, este puede ser un problema difícil de tratar, ya que a menudo también querrá desplazarse por la pantalla o ajustar la posición del campo de texto para que sea fácil de ver al editar. Solo asegúrate de hacer muchas pruebas al entrar y salir de los campos de texto de diferentes maneras y también salir temprano (siempre da al usuario la opción de cerrar el teclado en lugar de pasar al siguiente campo, generalmente con "Listo" en la barra de navegación)

Kendall Helmstetter Gelner
fuente
9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
iKushal
fuente
No debe usar etiquetas para este propósito.
Christian Schnorr
7

Un método muy sencillo para cerrar el teclado cuando se presiona el botón 'Listo' es:

Cree una nueva IBAction en el encabezado

- (IBAction)textFieldDoneEditing:(id)sender;

En el archivo de implementación (archivo .m) agregue el siguiente método:

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

Luego, cuando venga a vincular la IBAction con el campo de texto, vincule al evento 'Terminó al salir'.

jcrowson
fuente
1
De nada, no navega por los campos de texto pero cierra el teclado y le permite seleccionar otro campo.
jcrowson
7

Primero configure la tecla de retorno del teclado en xib, de lo contrario puede escribir código en 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;
}
mahesh chowdary
fuente
7

Me sorprende la cantidad de respuestas que aquí no entiendo un concepto simple: navegar a través de los controles en su aplicación no es algo que las vistas mismas deberían hacer. Es el trabajo del controlador decidir qué control hacer el próximo primer respondedor.

Además, la mayoría de las respuestas solo se aplican a la navegación hacia adelante, pero los usuarios también pueden querer retroceder.

Así que esto es lo que se me ocurrió. Su formulario debe ser administrado por un controlador de vista, y los controladores de vista son parte de la cadena de respuesta. Así que eres perfectamente libre de implementar los siguientes métodos:

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

Se puede aplicar una lógica adicional para desplazarse fuera de la pantalla de los respondedores visibles de antemano.

Otra ventaja de este enfoque es que no necesita subclasificar todo tipo de controles que desee mostrar (como UITextFields), sino que puede administrar la lógica a nivel de controlador, donde, seamos honestos, es el lugar correcto para hacerlo .

Christian Schnorr
fuente
Encuentro su respuesta más relevante, pero he notado algo divertido que quizás pueda aclarar. Al usar UITextFields que no están siendo administrados por un UIViewController, al presionar la tecla de tabulación, el siguiente UITextField será el primer respondedor por defecto. Sin embargo, cuando cada uno es administrado por un VC, esto no es cierto. Mis VC no están haciendo nada, pero la funcionalidad integrada de tabulación ha desaparecido. Me di cuenta de que los comandos de teclado del VC terminan siendo agregados a la cadena de respuesta de la vista. Esto me lleva a creer que si está utilizando un VC, es obligatorio implementar comandos de teclado.
Joey Carson
4

He agregado a la respuesta de PeyloW en caso de que esté buscando implementar una funcionalidad de botón anterior / siguiente:

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

    }
}

Donde subclases la UIView así:

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

Hola a todos por favor vean este

- (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;    
  }
}
rithik
fuente
En lugar de UIView * respondedor, sería más exacto usar UIRespoder * respondedor
Pedro Borges
4

Traté de resolver este problema utilizando un enfoque más sofisticado basado en la asignación de cada celda (o UITextField) en un UITableViewvalor de etiqueta único que luego se puede recuperar: active-next-uitextfield-in-uitableview-ios

¡Espero que esto ayude!

Fabiano Francesconi
fuente
4

Acabo de crear un nuevo Pod al tratar con estas cosas GNTextFieldsCollectionManager . Maneja automáticamente el siguiente / último problema de textField y es muy fácil de usar:

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

Toma todos los campos de texto ordenados al aparecer en la jerarquía de vistas (o por etiquetas), o puede especificar su propia matriz de campos de texto.

JakubKnejzlik
fuente
4

Solución en Swift 3.1, después de conectar sus campos de texto IBOutlets configure su delegado de campos de texto en viewDidLoad, y luego navegue su acción en 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
        }

    }
Krishna Thakur
fuente
4

Si alguien quiere así. Creo que este es el más cercano a los requisitos solicitados en cuestión

ingrese la descripción de la imagen aquí

Así es como he implementado este

Agregue la vista de accesorios para cada campo de texto para el que desea la configuración, utilizando

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
}

Use las siguientes funciones para manejar grifos

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
}

Deberá asignar las etiquetas textFields en secuencia en la que desea que responda el botón siguiente / anterior.

Mohammad Sadiq
fuente
3

Prefiero preferir:

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

En el archivo NIB, engancho los textFields en el orden deseado en esta matriz inputFields. Después de eso, hago una prueba simple para el índice del UITextField que informa que el usuario presionó 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;
}
Anticro
fuente
3
if (celda == nil)
{
    celda = [[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]; // Inicializar matriz mutable en ViewDidLoad
}

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

    etiqueta int = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: etiqueta];
    [txt BecomeFirstResponder];
    devuelva SÍ;
}
Rebecca Curties
fuente
3

Tenía alrededor de 10+ UITextField en mi guión gráfico y la forma en que habilité la siguiente funcionalidad fue creando una matriz de UITextField y convirtiendo al siguiente UITextField en el primer Respondedor. Aquí está el archivo de implementación:

#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
Argumento ilegal
fuente
3

Esto funcionó para mí en Xamarin.iOS / Monotouch. Cambie el botón del teclado a Siguiente, pase el control al siguiente UITextField y oculte el teclado después del último 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.
    };
  }
}

Dentro de ViewDidLoad tendrás:

Si sus TextFields no tienen una etiqueta, configúrela ahora:

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

y solo la llamada

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.
Daniele D.
fuente
3

Esta es una solución simple en forma rápida, sin usar etiquetas, sin trucos del guión gráfico ...

Solo usa esta extensión:

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
    }

}

Y llámelo así (por ejemplo) con su delegado de campo de texto:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    textField.nextTextFieldField()?.becomeFirstResponder()
    return true
}
Kevin ABRIOUX
fuente
Un comentario sobre esto es que su método asume que todos los campos están apilados verticalmente en una sola supervista. No permite campos agrupados y no funciona cuando los campos de texto pueden estar uno al lado del otro. Lo último es especialmente importante en estos días cuando uno usa clases de tamaño para reorganizar formularios para vistas horizontales y cuando los formularios se muestran en tabletas.
Michael Long
3

Una forma más segura y directa, suponiendo:

  • los delegados de campo de texto se configuran en su controlador de vista
  • todos los campos de texto son subvistas de la misma vista
  • los campos de texto tienen etiquetas en el orden en que desea progresar (por ejemplo, textField2.tag = 2, textField3.tag = 3, etc.)
  • pasará al siguiente campo de texto cuando toque el botón de retorno en el teclado (puede cambiarlo a siguiente , listo , etc.)
  • desea que el teclado se descarte después del último campo de texto

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

    }
}
jason z
fuente
2

en textFieldShouldReturn debe verificar que el campo de texto en el que se encuentra actualmente no sea el último cuando hacen clic en Siguiente y si no se cierra el teclado.

Daniel
fuente
Hola, está bien, pero ¿cómo puedo saltar al siguiente campo de texto con el botón "Siguiente"?
phx
2

Esta es una publicación antigua, pero tiene un alto rango de página, por lo que intervendré con mi solución.

Tuve un problema similar y terminé creando una subclase de UIToolbarpara administrar la funcionalidad siguiente / anterior / terminada en una tabla dinámica Ver con secciones: https://github.com/jday001/DataEntryToolbar

Establece la barra de herramientas como inputAccessoryView de sus campos de texto y los agrega a su diccionario. Esto le permite recorrerlos hacia adelante y hacia atrás, incluso con contenido dinámico. Existen métodos delegados si desea activar su propia funcionalidad cuando se realiza la navegación textField, pero no tiene que ocuparse de administrar ninguna etiqueta o estado del primer respondedor.

Hay fragmentos de código y una aplicación de ejemplo en el enlace de GitHub para ayudar con los detalles de implementación. Necesitará su propio modelo de datos para realizar un seguimiento de los valores dentro de los campos.

jday
fuente
2

Sin etiquetas de uso y sin agregar una propiedad para nextField / nextTextField, puede intentar esto para emular TAB, donde "testInput" es su campo activo actual:

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];
                                                    }];
Coeur
fuente
2

He estado usando la respuesta de Michael G. Emmons durante aproximadamente un año, funciona muy bien. Hace poco noté que llamar a resignFirstResponder y luego convertirse enFirstResponder de inmediato puede hacer que el teclado "falle", desaparezca y luego aparezca de inmediato. Cambié su versión ligeramente para omitir el resignFirstResponder si nextField está disponible.

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

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

        }
        más{
            [textField resignFirstResponder];
        }
    }
    más{
        [textField resignFirstResponder];
    }

    volver verdadero;

}
arinmorf
fuente