Como navegar pelos campos de texto (botões Avançar / Concluído)

487

Como posso navegar por todos os meus campos de texto com o botão "Avançar" no teclado do iPhone?

O último campo de texto deve fechar o teclado.

Eu configurei o IB the Buttons (Next / Done), mas agora estou preso.

Eu implementei a ação textFieldShouldReturn, mas agora os botões Avançar e Concluído fecham o teclado.

phx
fonte
Por favor, dê uma olhada na minha resposta abaixo. Na minha experiência, é uma solução melhor e mais completa do que a resposta normalmente fornecida. Não faço ideia por que alguém atribuiria uma classificação negativa.
memmons 12/05
Eu tenho o problema semelhante, mas com Cordova, Phonegap Existe alguma maneira de resolvê-lo?

Respostas:

578

No Cocoa para Mac OS X, você tem a próxima cadeia de respostas, onde é possível perguntar ao campo de texto qual controle deve ser o próximo foco. É isso que faz a tabulação entre os campos de texto funcionar. Mas como os dispositivos iOS não possuem teclado, apenas toque, esse conceito não sobreviveu à transição para o Cocoa Touch.

Isso pode ser feito facilmente de qualquer maneira, com duas suposições:

  1. Todos os "tabs" UITextFieldestão na mesma visualização pai.
  2. Sua "ordem de tabulação" é definida pela propriedade da tag.

Supondo que você possa substituir textFieldShouldReturn: como este:

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

Adicione um pouco mais de código e as suposições também podem ser ignoradas.

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 a super visão do campo de texto for um UITableViewCell, o próximo respondedor será

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!
PeyloW
fonte
6
Às vezes, existem botões "próximo" e "anterior" na parte superior do teclado. Pelo menos no navegador.
Tim Büthe
31
Só para ser pedante, quero esclarecer algumas informações erradas neste post. No OS X, a ordem das guias é configurada pelo método setNextKeyView do NSView: (não setNextResponder :). A cadeia de resposta é separada disso: é uma hierarquia de objetos de resposta que manipula ações "não direcionadas", por exemplo, ações enviadas para a primeira resposta em vez de diretamente para um objeto controlador. A cadeia de resposta geralmente segue a hierarquia de exibição, até a janela e seu controlador e, em seguida, o delegado do aplicativo. Google "cadeia de respostas" para obter muitas informações.
davehayden
A super visão do campo de texto será a UITableViewCell. Você precisaria acessar o associado UITableViewe obter a célula que contém o campo de texto que você está procurando.
Michael Mior
13
Não se esqueça de adicionar UITextFieldDelegatee definir os delegados dos campos de texto.
Sal
1
Lembrete amigável para definir a opção TAG no storyboard (no meu caso). Você deve definir manualmente a tag TextFields em ordem crescente. (Primeiro é 0, segundo é 1 etc.) para que esta solução funcione.
Joakim
170

Existe uma solução muito mais elegante que me surpreendeu na primeira vez que a vi. Benefícios:

  • Mais próximo da implementação do campo de texto OSX, onde um campo de texto sabe para onde o foco deve seguir
  • Não depende da configuração ou uso de tags - que são frágeis da IMO para este caso de uso
  • Pode ser estendido para trabalhar com ambos UITextFielde UITextViewcontroles - ou qualquer controle da interface do usuário da entrada do teclado
  • Não bagunça seu controlador de exibição com o código delegado UITextField padrão
  • Integra-se perfeitamente ao IB e pode ser configurado através da opção familiar arrastar e soltar para conectar tomadas.

Crie uma subclasse UITextField que tenha uma IBOutletpropriedade chamada nextField. Aqui está o cabeçalho:

@interface SOTextField : UITextField

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

@end

E aqui está a implementação:

@implementation SOTextField

@end

No seu controlador de exibição, você criará o -textFieldShouldReturn:método delegate:

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

No IB, altere seu UITextFields para usar a SOTextFieldclasse Em seguida, também no IB, defina o delegado para cada um dos 'SOTextFields' para 'Proprietário do arquivo' (que é exatamente onde você coloca o código para o método de delegação - textFieldShouldReturn). A beleza desse design é que agora você pode simplesmente clicar com o botão direito do mouse em qualquer campo de texto e atribuir a saída nextField ao próximo SOTextFieldobjeto que você deseja que seja o próximo respondedor.

Atribuindo nextField no IB

Além disso, você pode fazer coisas legais como fazer um loop no campo de texto para que, depois que o último perca o foco, o primeiro receba o foco novamente.

Isso pode ser facilmente estendido para atribuir automaticamente a returnKeyTypede a SOTextFielda UIReturnKeyNextse houver um nextField atribuído - menos uma coisa configurada manualmente.

memmons
fonte
3
Eu realmente gosto da integração do IB com o uso de tags - particularmente nas versões mais recentes do Xcode, que integram bem o IB ao restante do IDE - e não ter que desordenar meu controlador com o código delegado textField padrão é um bônus.
memmons
4
Tags são mais fáceis, mas desta forma é melhor, e mais apropriado para um bom design OO
Brain2000
1
Eu realmente gosto dessa solução, mas estou usando cada campo de texto em visualizações de célula separadas em a UITableView. Embora eu tenha IBOutletpropriedades no controlador de exibição para cada campo de texto, ele não me nextFieldpermite vincular a propriedade a essas propriedades no Proprietário do arquivo. Algum conselho sobre como posso fazê-lo funcionar nesse cenário?
Jay Imerman
1
O @JayImerman IB não permitirá que você conecte tomadas entre células. Portanto, o IB não será aceito nesse cenário. No entanto, você pode conectar as propriedades nextField usando o código. textField1.nextField = textField2; textField2.nextField = textField3;, etc
memmons
2
Alguma chance de essa resposta ser atualizada para versões mais recentes do xcode e do ios?
lostintranslation
82

Aqui está um sem delegação:

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 usando a UIControlEventEditingDidEndOnExit UITextFieldação (principalmente desconhecida) .

Você também pode conectar isso facilmente no storyboard, para que nenhuma delegação ou código seja necessário.

Edit: na verdade, eu não consigo descobrir como conectar isso no storyboard. becomeFirstRespondernão parece ser uma ação oferecida para esse evento de controle, que é uma pena. Ainda assim, você pode conectar todos os seus campos de texto a uma única ação no seu ViewController, que determina qual o campo de texto com becomeFirstResponderbase no remetente (embora não seja tão elegante quanto a solução programática acima, para que o IMO faça isso com o código acima viewDidLoad).

mxcl
fonte
18
Solução muito simples e eficaz. O último da cadeia pode até provocar, por exemplo [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Obrigado @mxcl.
msrdjan
Como você faria isso no storyboard?
Pedro Borges
Esta é provavelmente a melhor resposta aqui.
Daspianist
Isso funcionou perfeitamente para mim, mesmo no Swift 2. Essa é uma solução muito mais simples e rápida do que adicionar delegados.
ton
2
Swift 4: txtFirstname.addTarget (txtLastname, action: #selector (becomeFirstResponder), para: UIControlEvents.editingDidEndOnExit)
realtimez
78

Aqui está a minha solução para este problema.

Para resolver isso (e porque eu odeio confiar em tags para fazer coisas), decidi adicionar uma propriedade personalizada ao objeto UITextField. Em outras palavras, eu criei uma categoria no UITextField assim:

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

Agora, aqui está como eu o 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 o método textFieldShouldReturn:

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

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

    return NO; 
}

Agora tenho um tipo de lista vinculada de UITextField, cada um sabendo quem é o próximo na fila.

Espero que ajude.

Anth0
fonte
11
Esta é a melhor solução e deve ser escolhida como a resposta.
Giovanni
4
Essa solução funcionou para mim, a única coisa que mudei foi criar o nextTextField e IBOutlet para que eu pudesse configurar o encadeamento no meu Storyboard.
precisa saber é o seguinte
Se eu pudesse salvar respostas favoritas, essa seria uma delas. Obrigado pela solução limpa!
Matt Becker
1
Observe que, se você preferir IB, também poderá definir essas propriedades como IBOutlets. . tenha cuidado com colisões de nomes - a Apple pode eventualmente adicionar isso ao UIResponder.
Jasper Blues
Esta solução é compatível com o construtor de interfaces? Quero dizer, ele pode usar IBOutlets e referências de storyboard em vez de definir o nextTextField programaticamente? O IB não parece permitir que você defina a classe UITextField para uma categoria.
Pedro Borges
46

Uma extensão rápida que aplica a resposta do mxcl para tornar isso particularmente fácil (adaptado ao swift 2.3 pelo 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)
    }
}

É fácil de usar:

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

A extensão definirá o botão de retorno como "Avançar" para todos, exceto o último campo, e "Concluído" para o último campo, e mudará o foco / dispensará o teclado quando estes forem tocados.

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 assim -

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
fonte
5
BTW, se você deseja que o Done execute uma ação, basta que o seu View Controller esteja em conformidade UITextFieldDelegatee implementado func textFieldDidEndEditing(textField: UITextField). Apenas volte mais cedo se textField != field3.
21716 Ryan
25

Uma maneira mais consistente e robusta é usar o NextResponderTextField Você pode configurá-lo totalmente a partir do construtor de interface, sem a necessidade de definir ou usar o delegado view.tag.

Tudo que você precisa fazer é

  1. Defina o tipo de classe de seu UITextFieldserNextResponderTextField insira a descrição da imagem aqui
  2. Em seguida, defina a saída do nextResponderFieldpara apontar para o próximo respondedor, pois pode ser qualquer coisa UITextFieldou qualquer UIRespondersubclasse. Também pode ser um UIButton e a biblioteca é inteligente o suficiente para acionar o TouchUpInsideevento do botão apenas se estiver ativado. insira a descrição da imagem aqui insira a descrição da imagem aqui

Aqui está a biblioteca em ação:

insira a descrição da imagem aqui

mohamede1945
fonte
Sim. Funciona muito bem com o Swift 3.1, solução muito elegante, exatamente como o IB deve funcionar imediatamente.
Richard
Desculpe, eu não recebi a pergunta.
precisa saber é o seguinte
1
NextResponderTextField funcionou para mim. Era rápido e fácil de usar, apenas um arquivo rápido, e fazia exatamente o que eu precisava sem problemas.
precisa
Isso funciona muito bem! Nota: tive que alterar a tecla Return manualmente para exibir "Next" e "Done" como no exemplo.
Mike Miller
14

Gosto das soluções OO que já foram sugeridas pelo Anth0 e pelo Answerbot. No entanto, eu estava trabalhando em um POC rápido e pequeno, então não queria desorganizar as coisas com subclasses e categorias.

Outra solução simples é criar um NSArray de campos e procurar o próximo campo quando você pressionar próximo. Não é uma solução OO, mas é rápida, simples e fácil de implementar. Além disso, você pode ver e modificar a ordem rapidamente.

Aqui está o meu código (criado com base em outras respostas neste tópico):

@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;
}
  • Eu sempre retorno NÃO porque não quero inserir uma quebra de linha. Apenas pensei em salientar que, quando retornasse YES, sairia automaticamente dos campos subsequentes ou inseriria uma quebra de linha no meu TextView. Levei um pouco de tempo para descobrir isso.
  • O activeField mantém o controle do campo ativo, caso seja necessário rolar a tela para desobstruir o campo do teclado. Se você tiver um código semelhante, atribua o activeField antes de alterar o primeiro respondedor. A alteração da primeira resposta é imediata e acionará o evento KeyboardWasShown imediatamente.
Letreiro
fonte
Agradável! Simples e o código é tudo em um modelo. AnthO's também é muito bom.
Seamus
Lembre-se de que você corre o risco de manter ciclos com este código. Seu fieldArray está convertendo as referências fracas usadas em todos os seus IBOutlets em referências fortes na própria matriz.
Michael Long
11

Aqui está uma implementação de tabulação usando uma categoria no UIControl. Esta solução tem todas as vantagens dos métodos de Michael e Anth0, mas funciona para todos os UIControls, não apenas para UITextFields. Também funciona perfeitamente com o Interface Builder e os storyboards.

Aplicativo de origem e exemplo: repositório GitHub para UIControlsWithTabbing

Uso:

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

Atribuindo nextControl no Interface Builder

Cabeçalho:

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

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

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

- (BOOL)transferFirstResponderToNextControl;

@end

Implementação:

#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
fonte
1
Às vezes, o próximo widget não é um controle, ou seja, UITextView e você pode também tabular. Considere alterar o tipo de UITextField para UIResponder.
Pedro Borges
1
Funciona muito bem e com documentação fantástica. Eu gostaria de rolar para baixo nesta pergunta várias horas atrás. :)
Javier Cadiz
10

Eu tentei muitos códigos e, finalmente, isso funcionou para mim no Swift 3.0 mais recente [março de 2017]

A ViewControllerclasse deve ser herdada UITextFieldDelegatepara fazer esse código funcionar.

class ViewController: UIViewController,UITextFieldDelegate  

Adicione o campo Texto com o número de etiqueta apropriada e esse número de etiqueta é usado para levar o controle ao campo de texto apropriado com base no número de etiqueta incremental atribuído a ele.

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

No código acima, o returnKeyType = UIReturnKeyType.nextwhere fará com que o teclado volte a ser exibido, pois Nextvocê também tem outras opções, Join/Goetc., com base no seu aplicativo, altere os valores.

Este textFieldShouldReturné um método controlado por UITextFieldDelegate e aqui temos a próxima seleção de campo com base no incremento do valor do 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
 }
BHUVANESH MOHANKUMAR
fonte
Isso funciona, exceto se o campo de entrada for UITextView. Alguma idéia de como fazer isso em combinação?
T9b
@ T9b O que você precisa exatamente? que tipo de controle você está usando no seu aplicativo?
BHUVANESH MOHANKUMAR
Existem dois tipos de entradas de texto, a primeira é UITextField(insira uma linha de texto) e a outra é UITextView(insira várias linhas de texto). UITextViewobviamente não responde a esse código, mas pode ser usado em formulários.
T9b
Sim, esta parte do código apenas para UITextField e você tentou modificar o evento para o UITextView?
BHUVANESH MOHANKUMAR
9

Depois de sair de um campo de texto, você chama [otherTextField becomeFirstResponder] e o próximo campo fica em foco.

Na verdade, esse pode ser um problema complicado, pois muitas vezes você também deseja rolar a tela ou ajustar a posição do campo de texto para facilitar a visualização ao editar. Apenas faça muitos testes para entrar e sair dos campos de texto de maneiras diferentes e também para sair mais cedo (sempre dê ao usuário a opção de descartar o teclado em vez de ir para o próximo campo, geralmente com "Concluído" em a barra de navegação)

Kendall Helmstetter Gelner
fonte
9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
iKushal
fonte
Você não deve usar tags para esse fim.
Christian Schnorr
7

Um método muito fácil de descartar o teclado quando o botão 'Concluído' é pressionado é:

Crie uma nova IBAction no cabeçalho

- (IBAction)textFieldDoneEditing:(id)sender;

No arquivo de implementação (arquivo .m), adicione o seguinte método:

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

Então, quando você vincular o IBAction ao campo de texto - vincule ao evento 'Terminou na saída'.

jcrowson
fonte
1
De nada, ele não navega pelos campos de texto, mas fecha o teclado e permite selecionar outro campo.
precisa saber é
7

Primeiro defina a tecla de retorno do teclado no xib, caso contrário, você poderá escrever o código em 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
fonte
7

Estou surpreso com quantas respostas aqui não conseguem entender um conceito simples: navegar pelos controles no seu aplicativo não é algo que as próprias visualizações devem fazer. É o controlador é decidir qual controle deve ser o próximo responsável pela resposta.

Além disso, a maioria das respostas se aplica apenas à navegação para frente, mas os usuários também podem querer retroceder.

Então aqui está o que eu criei. Seu formulário deve ser gerenciado por um controlador de exibição, e os controladores de exibição fazem parte da cadeia de respostas. Portanto, você está perfeitamente livre para implementar os seguintes 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];
}

Pode ser aplicada lógica adicional para rolar os respondedores fora da tela visíveis antes.

Outra vantagem dessa abordagem é que você não precisa subclassificar todos os tipos de controles que deseja exibir, UITextFieldmas pode gerenciar a lógica no nível do controlador, onde, sejamos honestos, é o lugar certo para fazer isso .

Christian Schnorr
fonte
Acho a sua resposta da maior relevância, mas notei algo engraçado que talvez você possa esclarecer. Ao usar UITextFields que não estão sendo gerenciados por um UIViewController, pressionar a tecla tab fará do próximo UITextField o primeiro respondedor por padrão. No entanto, quando cada um é gerenciado por um VC, isso não é verdade. Meus VCs não estão fazendo nada, mas a funcionalidade de abas incorporada se foi. Percebi que keyCommands do VC acaba sendo adicionado à cadeia de respostas da exibição. Isso me leva a acreditar que, se você estiver usando um VC, é obrigatório implementar keyCommands.
Joey Carson
4

Eu adicionei à resposta do PeyloW caso você esteja procurando implementar uma funcionalidade de botão anterior / seguinte:

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

    }
}

Onde você subclassifica o UIView assim:

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

Olá a todos, por favor, veja 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
fonte
Em vez de UIView * resposta que seria mais preciso usar UIRespoder * responder
Pedro Borges
4

Tentei resolver esse problema usando uma abordagem mais sofisticada baseada na atribuição de cada célula (ou UITextField) em um UITableViewvalor de tag exclusivo que pode ser recuperado posteriormente: enable-next-uitextfield-in-uitableview-ios

Eu espero que isso ajude!

Fabiano Francesconi
fonte
4

Acabei de criar um novo Pod ao lidar com esse material GNTextFieldsCollectionManager . Ele lida automaticamente com o próximo / último problema do textField e é muito fácil de usar:

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

Agarra todos os campos de texto classificados por aparecer na hierarquia de exibição (ou por tags), ou você pode especificar sua própria matriz de campos de texto.

JakubKnejzlik
fonte
4

Solução no Swift 3.1, depois de conectar seus campos de texto, IBOutlets defina seu delegado de campos de texto em viewDidLoad e navegue sua ação em 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
fonte
4

Se alguém quiser assim. Eu acho que este é o mais próximo dos requisitos solicitados em questão

insira a descrição da imagem aqui

Aqui está como eu implementei este

Adicione visualização acessória para cada campo de texto para o qual você deseja configurar, 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
}

Use as seguintes funções para lidar com torneiras

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
}

Você precisará fornecer as tags textFields na sequência em que deseja que o botão próximo / anterior responda.

Mohammad Sadiq
fonte
3

Prefiro:

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

No arquivo NIB, conecto os textFields na ordem desejada a esse array inputFields. Depois disso, faço um teste simples para o índice do UITextField que informa que o usuário tocou em retornar:

// 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
fonte
3
if (célula == nil)
{
    célula = [[alocação de UITableViewCell] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellIdentifier];
    txt_Input = [[alocação de UITextField] initWithFrame: CGRectMake (0, 10, 150, 30)];
    txt_Input.tag = indexPath.row + 1;
    [self.array_Textfields addObject: txt_Input]; // Inicializa a matriz mutável em ViewDidLoad
}

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

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt becomeFirstResponder];
    retornar SIM;
}
Rebecca Curties
fonte
3

Eu tinha cerca de 10+ UITextField no meu storyboard e a maneira como habilitei a próxima funcionalidade foi criar uma matriz de UITextField e tornar o próximo UITextField o firstResponder. Aqui está o arquivo de implementação:

#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
fonte
3

Isso funcionou para mim no Xamarin.iOS / Monotouch. Altere o botão do teclado para Avançar, passe o controle para o próximo UITextField e oculte o teclado após o ú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 do ViewDidLoad, você terá:

Se seus TextFields não tiverem um Tag, defina-o agora:

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

e apenas a ligação

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.
fonte
3

Esta é uma solução simples, rápida, sem uso de tags, sem truques de storyboard ...

Basta usar esta extensão:

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 chame assim (por exemplo) com seu delegado do campo de texto:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    textField.nextTextFieldField()?.becomeFirstResponder()
    return true
}
Kevin ABRIOUX
fonte
Um comentário é que seu método assume que todos os campos estão empilhados verticalmente em uma única superview. Ele não permite campos agrupados e não funciona quando os campos de texto podem estar lado a lado. O último é especialmente importante nos dias de hoje, quando se usa classes de tamanho para reorganizar formulários para visualizações horizontais e quando os formulários são exibidos em tablets.
Michael Long
3

Uma maneira mais segura e direta, assumindo:

  • os delegados do campo de texto estão definidos para o seu controlador de exibição
  • todos os campos de texto são subviews da mesma view
  • os campos de texto possuem tags na ordem em que você deseja progredir (por exemplo, textField2.tag = 2, textField3.tag = 3, etc.)
  • a mudança para o próximo campo de texto ocorrerá quando você tocar no botão de retorno no teclado (você pode alterar para próximo , pronto etc.)
  • você deseja que o teclado seja descartado após o ú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
fonte
2

em textFieldShouldReturn, verifique se o campo de texto em que você está atualmente não é o último quando clica em Avançar e se ele não dispensa o teclado.

Daniel
fonte
Ei, ok, isso funciona, mas como posso pular para o próximo campo de texto com o botão "Avançar"?
Phx
2

Este é um post antigo, mas tem um alto page rank, por isso vou falar com minha solução.

Eu tive um problema semelhante e acabei criando uma subclasse de UIToolbarpara gerenciar a funcionalidade próxima / anterior / concluída em uma tabela dinâmica.Ver com seções: https://github.com/jday001/DataEntryToolbar

Você define a barra de ferramentas como inputAccessoryView dos seus campos de texto e os adiciona ao seu dicionário. Isso permite que você alterne entre eles para frente e para trás, mesmo com conteúdo dinâmico. Existem métodos delegados se você deseja acionar sua própria funcionalidade quando a navegação textField acontece, mas você não precisa lidar com o gerenciamento de nenhuma tag ou status de respondedor.

Existem trechos de código e um aplicativo de exemplo no link do GitHub para ajudar nos detalhes da implementação. Você precisará de seu próprio modelo de dados para acompanhar os valores dentro dos campos.

jday
fonte
2

Sem usar tags e sem adicionar uma propriedade para nextField / nextTextField, você pode tentar emular TAB, onde "testInput" é o seu campo ativo atual:

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];
                                                    }];
Cœur
fonte
2

Estou usando a resposta de Michael G. Emmons há cerca de um ano, funciona muito bem. Recentemente, notei que chamar resignFirstResponder e, em seguida, tornar-seFirstResponder imediatamente pode causar uma falha no teclado, desaparecendo e aparecendo imediatamente. Alterei a versão dele ligeiramente para pular o resignFirstResponder se o nextField estiver disponível.

- (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] becomeFirstResponder]; });

        }
        outro{
            [textField resignFirstResponder];
        }
    }
    outro{
        [textField resignFirstResponder];
    }

    return true;

}
arinmorf
fonte