How do you set the tab order in iOS?

2019-01-17 09:14发布

Is there a way (either in IB or code) to set the tab order between text fields in a view?

Note that I'm not talking about the next form field after the return (or "Next") button is pressed -- many bluetooth keyboards have a tab key, which seems to cycle through the fields in completely different order. In my particular case, this order doesn't correspond to the fields' position in the view or even the order in which the fields were added. Modifying the xib file by hand to change the NSNextKeyView doesn't seem to make a difference either.

Does anyone know how to change this order?

8条回答
Luminary・发光体
2楼-- · 2019-01-17 10:02

The Tab key behaviour in ios will be as follows:- when u press tab on external keyboard- the control traverses across all the textfields in that screen by calling only shouldBeginEditing method where its return value is also determined by Apple which cant be override. After scanning all the fields it calculates nearest x positioned Textfield relative to view offset from the current Textfield and then nearest Y Positioned Field.

Also can't be done anything until control comes to textFieldDidBeginEditing method.

Reason for apple's restriction might be to let devs to follow the guidelines of UI where next responder of field should be it's closest positioned Field rather than any other field .

查看更多
贪生不怕死
3楼-- · 2019-01-17 10:08

@sprocket's answer was only somewhat helpful. Just because something works out of the box doesn't mean you should stop thinking about a better way -- or even the right way -- of doing something. As he noticed the behavior is undocumented but fits our needs most of the time.

This wasn't enough for me though. Think of a RTL language and tabs would still tab left-to-right, not to mention the behavior is entirely different from simulator to device (device doesn't focus the first input upon tab). Most importantly though, Apple's undocumented implementation seems to only consider views currently installed in the view hierarchy.

Think of a form in form of (no pun intended) a table view. Each cell holds a single control, hence not all form elements may be visible at the same time. Apple would just cycle back up once you reached the bottommost (on screen!) control, instead of scrolling further down. This behavior is most definitely not what we desire.

So here's what I've come up with. Your form should be managed by a view controller, and view controllers are part of the responder chain. So you're perfectly free to implement the following methods:

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

Additional logic for scrolling offscreen responders visible beforehand may apply.

Another advantage of this approach is that you don't need to subclass all kinds of controls you may want to display (like UITextFields) but can instead manage the logic at controller level, where, let's be honest, is the right place to do so.

查看更多
太酷不给撩
5楼-- · 2019-01-17 10:15

I solved this by subclassing UITextField as NextableTextField. That subclass has a property of class UITextField with IBOutlet a hookup.

Build the interface in IB. Set the class of your text field to NextableTextField. Use the connections Inspector to drag a connection to the 'next' field you want to tab to.

In your text field delegate class, add this delegate method...

- (BOOL)textFieldShouldReturn:(UITextField *) textField
{
    BOOL didResign = [textField resignFirstResponder];
    if (!didResign) return NO;

    if ([textField isKindOfClass:[NextableTextField class]])
        dispatch_async(dispatch_get_current_queue(), ^{ [[(NextableTextField *)textField nextField] becomeFirstResponder]; });

    return YES;
}

BTW - I didn't come up with this; just remember seeing someone else's idea.

查看更多
Rolldiameter
6楼-- · 2019-01-17 10:16

You can do this by setting the tag for each textfield and handling this in the textfieldShouldReturn method.

See this blogpost about it: http://iphoneincubator.com/blog/windows-views/how-to-create-a-data-entry-screen

查看更多
冷血范
7楼-- · 2019-01-17 10:17

I'm interested in solving the same problem, although so far the default order, which appears to be left to right, then top to bottom, is the one I want.

I tested the hypothesis that the cursor moves in depth-first order through the tree of subviews and superview, but that is not true. Changing the order of subviews without changing their location didn't change the order of fields traversed by tab presses.

One possibly useful feature is that the text field delegate's textFieldShouldBeginEditing method appears to be called for every text field in the application's window. If that returns NO, then the text field won't be chosen, so if you can define your desired order and make only the right one return YES, that might solve your problem.

查看更多
登录 后发表回答