如何浏览文本字段(“下一步”/“完成”按钮)

How to navigate through textfields (Next / Done Buttons)

提问人:phx 提问时间:8/28/2009 最后编辑:piccianophx 更新时间:12/21/2021 访问量:222121

问:

如何使用 iPhone 键盘上的“下一步”按钮浏览所有文本字段?

最后一个文本字段应关闭键盘。

我已经设置了 IB 按钮(下一步/完成),但现在我被卡住了。

我实现了textFieldShouldReturn操作,但现在Next和Done按钮关闭了键盘。

iOS Objective-C iPhone

评论

0赞 memmons 5/12/2011
请看下面我的回答。根据我的经验,这是一个比通常给出的答案更好、更完整的解决方案。我不知道为什么有人会给它一个负面评价。
0赞 10/21/2015
我有类似的问题,但是使用Cordova,Phonegap有什么方法可以解决它吗?

答:

2赞 Daniel 8/28/2009 #1

在textFieldShouldReturn中,当他们单击“下一步”时,您应该检查您当前所在的文本字段是否不是最后一个,如果它没有关闭键盘。

评论

0赞 phx 8/28/2009
嘿,好的,这有效,但是我怎样才能用“下一步”按钮跳到下一个文本字段?
9赞 Kendall Helmstetter Gelner 8/29/2009 #2

退出一个文本字段后,调用 [otherTextField becomeFirstResponder],下一个字段将获得焦点。

这实际上可能是一个棘手的问题,因为您通常还需要滚动屏幕或以其他方式调整文本字段的位置,以便在编辑时轻松查看。只要确保做大量的测试,以不同的方式进入和离开文本字段,并提前离开(总是给用户一个关闭键盘的选项,而不是转到下一个字段,通常在导航栏中使用“完成”)

593赞 PeyloW 8/29/2009 #3

在 Cocoa for Mac OS X 中,你有下一个响应器链,你可以在其中询问文本字段下一个应该有焦点的控件。这就是使文本字段之间的 Tab 键起作用的原因。但是由于iOS设备没有键盘,只有触摸,因此这个概念在向Cocoa Touch的过渡中并没有幸存下来。

无论如何,这都可以很容易地完成,但有两个假设:

  1. 所有“tabbable”都位于同一父视图上。UITextField
  2. 它们的“制表符顺序”由 tag 属性定义。

假设这样,您可以覆盖textFieldShouldReturn:,如下所示:

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

再添加一些代码,假设也可以忽略。

斯威夫特 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
}

如果文本字段的 superview 将是 UITableViewCell,则下一个响应者将是

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

评论

6赞 Tim Büthe 10/18/2010
有时键盘顶部有“下一个”和“上一个”按钮。至少在浏览器中。
32赞 davehayden 1/11/2012
为了迂腐,我想在这篇文章中澄清一些错误信息。在 OS X 上,Tab 键顺序是通过 NSView 的 setNextKeyView:(不是 setNextResponder:) 方法设置的。响应器链与此分开:它是处理“无目标”操作的响应器对象的层次结构,例如,发送到第一个响应者而不是直接发送到控制器对象的操作。响应程序链通常遵循视图层次结构,直至窗口及其控制器,然后是应用委托。谷歌“响应链”以获取大量信息。
14赞 Sal 2/28/2014
不要忘记添加和设置 textfields 委托。UITextFieldDelegate
2赞 Joakim 1/7/2016
友情提醒从情节提要中设置 TAG 选项(在我的情况下)。您必须按升序手动设置 TextFields 标记。(第一个是 0,第二个是 1,以此类推)使此解决方案起作用。
1赞 JohnV 2/23/2016
正如@Sal所指出的,不要忘记控制将所有文本字段拖动到“视图控制器”按钮并将它们设置为委托。在你这样做之前,什么都不会发生。因此,如果您执行了上述所有操作,并且它不会跳转到下一个,则可能是您的错误。
4赞 rithik 3/11/2011 #4

大家好,请看这个

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

评论

0赞 Pedro Borges 9/15/2014
使用 UIRespoder *responder 而不是 UIView *responder 会更准确
172赞 memmons 5/5/2011 #5

有一个更优雅的解决方案,当我第一次看到它时,它让我大吃一惊。好处:

  • 更接近 OSX 文本字段实现,其中文本字段知道下一步应该关注的地方
  • 不依赖于设置或使用标签 - 对于此用例而言,IMO 是脆弱的
  • 可以扩展为同时使用 和 控件 -- 或任何键盘输入 UI 控件UITextFieldUITextView
  • 不会使用样板 UITextField 委托代码弄乱视图控制器
  • 与IB很好地集成,可以通过熟悉的选项拖放进行配置以连接插座。

创建一个 UITextField 子类,该子类具有名为 nextField 的属性。标题如下:IBOutlet

@interface SOTextField : UITextField

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

@end

下面是实现:

@implementation SOTextField

@end

在视图控制器中,您将创建委托方法:-textFieldShouldReturn:

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

在 IB 中,更改 UITextFields 以使用该类。接下来,同样在 IB 中,将每个“SOTextFields”的委托设置为“File's Owner”(就在您放置委托方法代码 - textFieldShouldReturn 的位置)。这种设计的美妙之处在于,现在您只需右键单击任何 textField,即可将 nextField 出口分配给您希望成为下一个响应者的下一个对象。SOTextFieldSOTextField

Assigning nextField in IB

此外,您可以做一些很酷的事情,例如循环 textFields,以便在最后一个失去焦点后,第一个将再次获得焦点。

这可以很容易地扩展为在分配了 nextField 时自动将 分配给 -- 手动配置少了一件事。returnKeyTypeSOTextFieldUIReturnKeyNext

评论

3赞 memmons 7/21/2011
我真的很喜欢 IB 集成而不是使用标签——尤其是新版本的 Xcode,它将 IB 与 IDE 的其余部分很好地集成在一起——而且不必用样板 textField 委托代码弄乱我的控制器是一个奖励。
4赞 Brain2000 12/15/2011
标签更容易,但这种方式更好,更适合一个好的OO设计
1赞 Jay Imerman 6/22/2012
我真的很喜欢这个解决方案,但我在单独的单元格视图中使用每个文本字段。尽管我在视图控制器上为每个文本字段提供了属性,但它不允许我将属性链接到文件所有者上的这些属性。有什么建议如何让它在这种情况下工作?UITableViewIBOutletnextField
1赞 memmons 11/8/2012
@JayImerman IB 将不允许您在电池之间连接插座。因此,在这种情况下,IB将是一个不可行的选择。但是,您可以使用代码挂接 nextField 属性。等。textField1.nextField = textField2; textField2.nextField = textField3;
2赞 lostintranslation 8/13/2014
这个答案有可能针对较新版本的 xcode 和 ios 进行更新吗?
78赞 Anth0 12/2/2011 #6

这是我对这个问题的解决方案。

为了解决这个问题(因为我讨厌依赖标签来做事),我决定向 UITextField 对象添加一个自定义属性。换句话说,我在UITextField上创建了一个类别,如下所示:

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

现在,这是我如何使用它:

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

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

并实现 textFieldShouldReturn 方法:

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

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

    return NO; 
}

我现在有一个 UITextField 的链接列表,每个链接列表都知道谁是下一个。

希望它会有所帮助。

评论

11赞 Giovanni 10/22/2012
这是最好的解决方案,应该被选为答案。
4赞 zachzurn 1/28/2013
这个解决方案对我有用,我唯一改变的是制作 nextTextField 和 IBOutlet,这样我就可以从我的 Storyboard 中设置链接。
0赞 Matt Becker 8/9/2013
如果我能保存最喜欢的答案,这将是其中之一。感谢您的干净解决方案!
1赞 Jasper Blues 9/5/2013
请注意,如果您喜欢 IB,您还可以将这些属性设置为 IBOutlets。.小心名称冲突 - Apple 最终可能会将其添加到 UIResponder 中。
0赞 Pedro Borges 9/13/2014
此解决方案是否与接口生成器兼容?我的意思是它可以使用 IBOutlets 和情节提要引用而不是以编程方式设置 nextTextField 吗?IB 似乎不允许将 UITextField 类设置为类别。
2赞 Cœur 2/20/2012 #7

如果不使用 tags 并且不添加 nextField/nextTextField 的属性,您可以尝试此来模拟 TAB,其中“testInput”是您当前的活动字段:

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];
                                                    }];
7赞 jcrowson 3/23/2012 #8

按下“完成”按钮时关闭键盘的一种非常简单的方法是:

在标头中创建新的 IBAction

- (IBAction)textFieldDoneEditing:(id)sender;

在实现文件(.m 文件)中,添加以下方法:

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

然后,当您将 IBAction 链接到文本字段时 - 链接到“Did End On Exit”事件。

评论

1赞 jcrowson 4/24/2012
不客气,它不会浏览文本字段,但会关闭键盘并允许您选择另一个字段。
14赞 Marquee 5/31/2012 #9

我喜欢 Anth0 和 Answerbot 已经建议的 OO 解决方案。但是,我正在开发一个快速而小的 POC,所以我不想用子类和类别来混淆东西。

另一个简单的解决方案是创建一个字段的 NSArray,并在按下一步时查找下一个字段。不是面向对象的解决方案,而是快速、简单且易于实施。此外,您可以一目了然地查看和修改排序。

这是我的代码(基于此线程中的其他答案构建):

@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;
}
  • 我总是返回 NO,因为我不想插入换行符。只是想我会指出这一点,因为当我返回 YES 时,它会自动退出后续字段或在我的 TextView 中插入换行符。我花了一点时间才弄清楚这一点。
  • activeField 会跟踪活动字段,以防需要滚动以从键盘上清除字段。如果您有类似的代码,请确保在更改第一响应者之前分配 activeField。更改第一响应程序是立即的,并将立即触发 KeyboardWasShown 事件。

评论

0赞 Seamus 4/1/2014
好!简单,代码都在一个模型中。AnthO's也非常好。
0赞 Michael Long 1/20/2017
请记住,使用此代码将面临保留周期的风险。fieldArray 正在将所有 IBOutlet 中使用的弱引用转换为数组本身中的强引用。
4赞 James Mertz 6/24/2012 #10

我已添加到 PeyloW 的答案中,以防您希望实现上一个/下一个按钮功能:

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

    }
}

其中,您可以像这样对 UIView 进行子类化:

@implementation UIView (FindAndReturnFirstResponder)
- (UITextView *)findFirstResponderAndReturn
{
    for (UITextView *subView in self.subviews) {
        if (subView.isFirstResponder){
            return subView;
        }
    }
    return nil;
}
@end
9赞 iKushal 8/3/2012 #11
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}

评论

0赞 Christian Schnorr 6/17/2015
您不应为此目的使用标签。
4赞 Fabiano Francesconi 2/8/2013 #12

我试图使用一种更复杂的方法解决这个问题,该方法基于在以后可以检索的唯一标签值中分配每个单元格(或):activate-next-uitextfield-in-uitableview-iosUITextFieldUITableView

我希望这会有所帮助!

3赞 Anticro 8/26/2013 #13

我宁愿:

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

在 NIB 文件中,我将所需顺序的 textFields 挂接到此 inputFields 数组中。之后,我对 UITextField 的索引进行了简单的测试,该索引报告用户点击返回:

// 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;
}
7赞 mahesh chowdary 11/26/2013 #14

首先在xib中设置键盘回车键,否则可以在: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;
}
92赞 mxcl 3/1/2014 #15

下面是一个没有委托的:

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

使用(大多数未知的)动作工作。UIControlEventEditingDidEndOnExitUITextField

您还可以轻松地将其挂接到情节提要中,因此不需要委派代码。

编辑:实际上我无法弄清楚如何在故事板中将其连接起来。 似乎没有为这个控制事件提供动作,这很遗憾。不过,您可以将所有文本字段挂接到 ViewController 中的单个操作,然后根据发送者确定要使用的 textField(尽管它不像上面的编程解决方案那样优雅,因此 IMO 使用上述代码来完成)。becomeFirstResponderbecomeFirstResponderviewDidLoad

评论

19赞 msrdjan 4/7/2014
非常简单有效的解决方案。链中的最后一个甚至可以触发,例如.谢谢@mxcl。[tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];
0赞 Pedro Borges 9/15/2014
你会如何在故事板中做到这一点?
1赞 daspianist 1/16/2015
这可能是这里最好的答案。
0赞 ton 11/19/2015
即使在 Swift 2 中,这对我来说也非常有效。这是一个比添加委托更简单、更快捷的解决方案。
2赞 realtimez 10/26/2017
Swift 4:txtFirstname.addTarget(txtLastname,操作:#selector(becomeFirstResponder),for:UIControlEvents.editingDidEndOnExit)
12赞 picciano 6/19/2014 #16

下面是在 UIControl 上使用类别的 Tab 键实现。此解决方案具有 Michael 和 Anth0 方法的所有优点,但适用于所有 UIControls,而不仅仅是 s。它还可以与Interface Builder和故事板无缝协作。UITextField

源和示例应用:UIControlsWithTabbing 的 GitHub 存储库Source and sample app: GitHub repository for UIControlsWithTabbing

用法:

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

Assigning nextControl in Interface Builder

页眉:

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

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

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

- (BOOL)transferFirstResponderToNextControl;

@end

实现:

#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赞 Pedro Borges 9/15/2014
有时,下一个小部件不是控件,即 UITextView,您可能也希望在那里按 Tab 键。请考虑将类型从 UITextField 更改为 UIResponder。
1赞 Javier Cadiz 9/26/2014
效果很好,文档也很棒。我希望我在几个小时前向下滚动到这个问题。:)
3赞 Rebecca Curties 8/1/2014 #17
if (cell == nil)
{
    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]; // Initialize mutable array in ViewDidLoad
}

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

    int tag = ( int) textField.tag ;
    UITextField * txt = [  self.array_Textfields objectAtIndex:tag ] ;
    [ txt becomeFirstResponder] ;
    return YES ;
}
3赞 Illegal Argument 11/4/2014 #18

我的故事板中大约有 10+ 个 UITextField,我启用下一个功能的方式是创建一个 UITextField 数组并使下一个 UITextField 成为 firstResponder。下面是实现文件:

#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
2赞 jday 1/27/2015 #19

这是一篇旧文章,但页面排名很高,所以我会附和我的解决方案。

我遇到了类似的问题,最终创建了一个子类,用于在动态 tableView 中管理下一个/上一个/完成的功能,其中包含以下部分: https://github.com/jday001/DataEntryToolbarUIToolbar

将工具栏设置为文本字段的 inputAccessoryView,并将其添加到其字典中。这允许您向前和向后循环浏览它们,即使是动态内容也是如此。如果要在 textField 导航发生时触发自己的功能,则可以使用委托方法,但不必处理管理任何标记或第一响应者状态。

GitHub 链接上有代码片段和示例应用程序,可帮助了解实现细节。您将需要自己的数据模型来跟踪字段中的值。

2赞 arinmorf 3/18/2015 #20

我已经使用Michael G. Emmons的答案大约一年了,效果很好。我最近确实注意到,立即调用 resignFirstResponder 然后成为 befrontResponder 可能会导致键盘“故障”,消失然后立即出现。如果nextField可用,我稍微更改了他的版本以跳过resignFirstResponder。

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

        }
        else{
            [textField resignFirstResponder];
        }
    }
    else{
        [textField resignFirstResponder];
    }

    return true;

}
8赞 Christian Schnorr 6/17/2015 #21

令我惊讶的是,这里有多少答案无法理解一个简单的概念:在应用程序中的控件中导航不是视图本身应该做的事情。控制器的工作是决定将哪个控制作为下一个第一响应者。

此外,大多数答案仅适用于向前导航,但用户可能也希望向后导航。

这就是我想出的。表单应由视图控制器管理,视图控制器是响应程序链的一部分。因此,您可以完全自由地实现以下方法:

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

可能适用用于滚动事先可见的屏幕外响应者的其他逻辑。

这种方法的另一个优点是,你不需要对你可能想要显示的所有类型的控件(如s)进行子类化,而是可以在控制器级别管理逻辑,老实说,这是这样做的正确位置UITextField

评论

0赞 Joey Carson 1/29/2016
我发现你的答案最相关,但我注意到一些有趣的事情,也许你可以澄清一下。使用不受 UIViewController 管理的 UITextField 时,默认情况下,按 Tab 键将使下一个 UITextField 成为第一个响应程序。但是,当每个都由 VC 管理时,情况并非如此。我的 VC 什么也没做,但内置的 Tab 键功能不见了。我注意到 VC 的 keyCommands 最终被添加到视图的响应器链中。这让我相信,如果您使用的是 VC,则必须实现 keyCommands。
2赞 Amit Shelgaonkar 6/22/2015 #22

您可以使用 IQKeyboardManager 库来执行此操作。它可以处理所有事情,您不需要任何额外的设置。IQKeyboardManager 可通过 CocoaPods 获得,要安装它,只需将以下行添加到您的 Podfile 中:

pod 'IQKeyboardManager'

或 只需将 IQKeyBoardManager 目录从演示项目拖放到您的项目中即可。就是这样。 您可以从以下位置找到 IQKeyBoardManager 目录 https://github.com/hackiftekhar/IQKeyboardManager

25赞 mohamede1945 6/29/2015 #23

一种更一致和健壮的方法是使用 NextResponderTextField 您可以完全从接口构建器配置它,而无需设置委托或使用 .view.tag

您需要做的就是

  1. 将 your 的类类型设置为UITextFieldNextResponderTextField enter image description here
  2. 然后将 to 的出口设置为指向下一个响应者,它可以是任何东西或任何子类。它也可以是 UIButton,并且该库足够智能,只有在启用按钮时才会触发按钮的事件。nextResponderFieldUITextFieldUIResponderTouchUpInsideenter image description here enter image description here

以下是运行中的库:

enter image description here

评论

0赞 Richard 4/2/2017
是的。与 Swift 3.1 配合得非常好,这是一个非常优雅的解决方案,正是 IB 开箱即用的方式。
0赞 mohamede1945 4/2/2017
对不起,我没有得到这个问题。
1赞 Pat McG 6/23/2017
NextResponderTextField 对我有用。它快速且易于使用,只需一个快速文件,并且完全可以毫不费力地完成我需要的工作。
0赞 Mike Miller 4/5/2018
这很好用!注意:我必须手动更改Return键才能使其显示“Next”和“Done”,如示例所示。
4赞 JakubKnejzlik 8/21/2015 #24

在处理这些东西时,我刚刚创建了新的 Pod GNTextFieldsCollectionManager。它会自动处理下一个/最后一个 textField 问题,并且非常易于使用:

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

抓取按视图层次结构中显示(或按标记)排序的所有文本字段,或者您可以指定自己的 textField 数组。

3赞 Daniele D. 2/17/2016 #25

这在 Xamarin.iOS / Monotouch 中对我有用。 将键盘按钮更改为“下一步”,将控件传递给下一个 UITextField,并在最后一个 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.
    };
  }
}

ViewDidLoad 中,您将拥有:

如果您的 TextFields 没有 Tag,请立即设置它:

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

只是电话

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.
50赞 Amos Joshua 3/9/2016 #26

一个快速扩展,应用了 mxcl 的答案,使这变得特别容易(由 Traveler 改编为 swift 2.3):

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)
    }
}

它很容易使用:

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

扩展程序会将除最后一个字段外的所有字段的返回按钮设置为“下一步”,将最后一个字段设置为“完成”,并在点击这些按钮时转移焦点/关闭键盘。

斯威夫特< 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:像这样使用 -

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)
        }
    }

评论

6赞 Ryan 4/15/2016
顺便说一句,如果你想让 Done 执行一个动作,你可以让你的视图控制器符合 ,并实现 。如果早点回来就好了。UITextFieldDelegatefunc textFieldDidEndEditing(textField: UITextField)textField != field3
3赞 Kevin ABRIOUX 7/1/2016 #27

这是 swift 中的一个简单的解决方案,没有使用标签,没有故事板技巧......

只需使用此扩展:

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
    }

}

并像这样调用它(例如)与你的文本字段委托:

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

评论

0赞 Michael Long 1/20/2017
对此的一条评论是,您的方法假定所有字段都垂直堆叠在单个超级视图中。它不允许分组字段,并且在文本字段可能并排时不起作用。如今,当人们使用大小类重新排列水平视图的表单以及在平板电脑上显示表单时,后者尤为重要。
3赞 Justin Wright 2/23/2017 #28

这是 Anth0 的答案的 Swift 3 版本。我在这里发布它是为了帮助任何想要利用他的伟大答案的快速开发人员!当您设置关联对象时,我冒昧地添加了返回键类型“Next”。

extension UITextField {

  @nonobjc static var NextHashKey: UniChar = 0

  var nextTextField: UITextField? {
    get {
      return objc_getAssociatedObject(self, 
        &UITextField.NextHashKey) as? UITextField
    }
    set(next) {
     self.returnKeyType = UIReturnKeyType.next
     objc_setAssociatedObject(self,
      &UITextField.NextHashKey,next,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
  }
}

下面是另一个扩展,它显示了使用上述代码循环浏览 UITextFields 列表的可能性。

extension UIViewController: UITextFieldDelegate {
 public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
   guard let next = textField.nextTextField else {
     textField.resignFirstResponder()
     return true
   }

    next.becomeFirstResponder()
    return false
  }
}

然后在你的 ViewController 或任何地方,你可以像这样设置你的文本字段......

@IBOutlet fileprivate weak var textfield1: UITextField!
@IBOutlet fileprivate weak var textfield2: UITextField!
@IBOutlet fileprivate weak var textfield3: UITextField!

...

[textfield1, textfield2, textfield3].forEach{ $0?.delegate = self }

textfield1.nextTextField = textfield2
textfield2.nextTextField = textfield3
// We don't assign a nextTextField to textfield3 because we want 
// textfield3 to be the last one and resignFirstResponder when 
// the return button on the soft keyboard is tapped.
10赞 BHUVANESH MOHANKUMAR 3/23/2017 #29

我尝试了很多代码,最后,这在 Swift 3.0 最新 [2017 年 3 月] 中对我有用

应继承该类以使此代码正常工作。ViewControllerUITextFieldDelegate

class ViewController: UIViewController,UITextFieldDelegate  

添加具有正确标记号的文本字段,此标记号用于根据分配给它的增量标记号将控件控制到相应的文本字段。

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

在上面的代码中,where 将使键盘返回键显示,因为您还有其他选项,例如,根据您的应用程序更改值。returnKeyType = UIReturnKeyType.nextNextJoin/Go

这是 UITextFieldDelegate 控制的方法,这里我们根据 Tag 值递增选择下一个字段textFieldShouldReturn

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
 }

评论

0赞 T9b 5/2/2017
这可行,除非输入字段是 。知道如何组合执行此操作吗?UITextView
0赞 BHUVANESH MOHANKUMAR 5/2/2017
@T9b 您到底需要什么?你在应用中使用什么类型控件?
0赞 T9b 5/2/2017
有两种类型的文本输入,第一种是(输入单行文本),另一种是(输入多行文本)。 显然没有响应此代码,但它可以在表单中使用。UITextFieldUITextViewUITextView
0赞 BHUVANESH MOHANKUMAR 5/2/2017
是的,这部分代码仅适用于 UITextField,您是否尝试过修改 UITextView 的事件?
5赞 Krishna Kumar Thakur 4/25/2017 #30

在 Swift 3.1 中的解决方案,连接文本字段 IBOutlets 后,在 viewDidLoad 中设置文本字段委托,然后在 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
        }

    }
6赞 Mohammad Sadiq 7/29/2017 #31

如果有人想要这样。我认为这是最接近所要求的要求

enter image description here

这是我如何实现这个

为要设置的每个文本字段添加附件视图,使用

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
}

使用以下函数处理分流器

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
}

您需要按您希望下一个/上一个按钮响应的顺序提供 textFields 标记。

0赞 Maor 9/14/2017 #32

Swift 3 解决方案,使用UITextField's

func nextTextField() {
    let textFields = // Your textfields array

    for i in 0 ..< textFields.count{
        if let textfield = textFields[i], textfield.isFirstResponder{
            textfield.resignFirstResponder()
            if i+1 < textFields.count, let nextextfield = textFields[i+1]{
                nextextfield.becomeFirstResponder()
                return
            }
        }
    }
}
-2赞 realtimez 10/26/2017 #33

Swift 4 for mxcl 答案:

txtFirstname.addTarget(txtLastname, action: 
#selector(becomeFirstResponder), for: UIControlEvents.editingDidEndOnExit)
4赞 jason z 5/17/2018 #34

一种更安全、更直接的方式,假设:

  • 文本字段委托将设置为视图控制器
  • 所有文本字段都是同一视图的子视图
  • 文本字段具有按您想要进度顺序排列的标签(例如,textField2.tag = 2、textField3.tag = 3 等)
  • 当您点击键盘上的返回按钮时,将移动到下一个文本字段(您可以将其更改为下一个完成等)。
  • 您希望键盘在最后一个文本字段后关闭

斯威夫特 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

    }
}

评论

0赞 Giau Huynh 4/1/2022
哇,完美的解决方案!
1赞 Kevin Flachsmann 12/21/2021 #35

我实现了一个动态解决方案,用于在视图层次结构中浏览响应程序。这个想法是,在整个层次结构中搜索下一个可能的响应者,根据需要向前或向后。

用于搜索的 UIView 扩展:

UIView+Cat.h

- (UIResponder *)getNextFirstResponder;
- (UIResponder *)getPreviousFirstResponder;

UIView+Cat.m

- (UIResponder *)getPreviousFirstResponder {
    if (!self.superview) {
        return nil;
    }
    
    BOOL hasreachedself = NO;
    for (NSInteger i = self.superview.subviews.count-1; i >= 0; i--) {
        UIView *v = [self.superview.subviews objectAtIndex:i];
        if (v == self) {
            hasreachedself = YES;
            continue;
        }
        if (!hasreachedself) continue;
        
        if ([v canBecomeFirstResponder] && !v.hidden) {
            return v;
        }
        UIResponder *subResponder = [self getNextFirstResponderInView:v];
        if (subResponder) {
            return subResponder;
        }
    }
    
    //search hierachicaly in superviews
    return [self.superview getPreviousFirstResponder];
}

- (UIResponder *)getNextFirstResponder {
    if (!self.superview) {
        return nil;
    }
    
    BOOL hasreachedself = NO;
    for (UIView *v in self.superview.subviews) {
        if (v == self) {
            hasreachedself = YES;
            continue;
        }
        if (!hasreachedself) continue;
        
        if ([v canBecomeFirstResponder] && !v.hidden) {
            return v;
        }
        UIResponder *subResponder = [self getNextFirstResponderInView:v];
        if (subResponder) {
            return subResponder;
        }
    }
    
    //search hierachicaly in superviews
    return [self.superview getNextFirstResponder];
}

- (UIResponder *)getNextFirstResponderInView:(UIView *)view {
    if ([view canBecomeFirstResponder] && !view.hidden) {
        return view;
    }
    for (UIView *v in view.subviews) {
        UIResponder *subResponder = [self getNextFirstResponderInView:v];
        if (subResponder) {
            return subResponder;
        }
    }
    return nil;
}

用法:将 UIToolbar 添加到 UITextField / UITextView(在类别或子类中):

- (void)addToolbarInputAccessoryView {
    UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectZero];
    UIBarButtonItem *prev = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.up"] style:UIBarButtonItemStylePlain target:self action:@selector(moveToPreviousFirstResponder)];
    UIBarButtonItem *next = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.down"] style:UIBarButtonItemStylePlain target:self action:@selector(moveToNextFirstResponder)];
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(hideKeyboard)];
    toolbar.items = @[prev, next, space, done];
    
    if ([[UITextField appearance] keyboardAppearance] == UIKeyboardAppearanceDark) {
        toolbar.barStyle = UIBarStyleBlack;
        toolbar.translucent = YES;
        [toolbar setBarTintColor:[UIColor blackColor]];
        [toolbar setTintColor:[UIColor whiteColor]];
    }
    
    [toolbar sizeToFit];
    
    self.inputAccessoryView = toolbar;
}

- (void)hideKeyboard {
    [self resignFirstResponder];
}

- (void)moveToNextFirstResponder {
    UIResponder *next = [self getNextFirstResponder];
    if (next) {
        [next becomeFirstResponder];
    }
    else {
        [self resignFirstResponder];
    }
}

- (void)moveToPreviousFirstResponder {
    UIResponder *prev = [self getPreviousFirstResponder];
    if (prev) {
        [prev becomeFirstResponder];
    }
    else {
        [self resignFirstResponder];
    }
}