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

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

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

我实现了textFieldShouldReturn动作,但现在下一步和完成按钮关闭键盘。


当前回答

Swift 4 for mxcl答案:

txtFirstname.addTarget(txtLastname, action: 
#selector(becomeFirstResponder), for: UIControlEvents.editingDidEndOnExit)

其他回答

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

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

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

此外,大多数答案只适用于前进导航,但用户也可能想后退。

这就是我想到的。表单应该由视图控制器管理,视图控制器是响应器链的一部分。所以你可以完全自由地实现以下方法:

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

额外的逻辑滚动屏幕外的响应可见之前可能适用。

这种方法的另一个优点是,您不需要子类化您可能想要显示的所有类型的控件(如UITextFields),而是可以在控制器级别管理逻辑,老实说,在控制器级别管理逻辑是正确的。

Swift 3解决方案,使用UITextField的有序数组

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

我已经实现了在视图层次结构中通过Responders导航的动态解决方案。其思想是,在整个层次结构中搜索下一个可能的响应器,根据需要向前或向后搜索。

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

没有usings标签,也没有为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];
                                                    }];