Как перемещаться по текстовым полям (кнопки «Далее» / «Готово») - PullRequest
475 голосов
/ 28 августа 2009

Как я могу перемещаться по всем моим текстовым полям с помощью кнопки «Далее» на клавиатуре iPhone?

Последнее текстовое поле должно закрывать клавиатуру.

Я настроил IB кнопки (Далее / Готово), но теперь я застрял.

Я реализовал действие textFieldShouldReturn, но теперь кнопки Next и Done закрывают клавиатуру.

Ответы [ 34 ]

567 голосов
/ 29 августа 2009

В Какао для Mac OS X у вас есть следующая цепочка респондента, где вы можете спросить в текстовом поле, какой элемент управления должен фокусироваться дальше. Это то, что делает вкладки между текстовыми полями работать. Но поскольку устройства iOS не имеют клавиатуры, только сенсорные, эта концепция не пережила переход на Cocoa Touch.

В любом случае это легко сделать с двумя допущениями:

  1. Все «табулируемые» UITextField s находятся в одном родительском представлении.
  2. Их "порядок табуляции" определяется свойством тега.

Предполагая, что вы можете переопределить 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.
}

Добавьте еще немного кода, и предположения также могут быть проигнорированы.

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
}

Если супервизором текстового поля будет UITableViewCell, то следующим респондентом будет

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!
170 голосов
/ 05 мая 2011

Существует намного более элегантное решение, которое поразило меня, когда я впервые увидел его. Преимущества:

  • Ближе к реализации текстового поля OSX, где текстовое поле знает, куда должен идти фокус
  • Не полагается на установку или использование тегов - которые являются IMO хрупкими для этого варианта использования
  • Может быть расширен для работы как с элементами управления UITextField и UITextView, так и с любым элементом управления пользовательским интерфейсом ввода с клавиатуры
  • Не загромождает контроллер представления стандартным кодом делегата UITextField
  • Прекрасно интегрируется с IB и может быть настроен с помощью привычной опции-drag-drop для подключения розеток.

Создайте подкласс UITextField со свойством IBOutlet с именем nextField. Вот заголовок:

@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 на использование класса SOTextField. Затем, также в IB, установите делегат для каждого из 'SOTextFields' в 'Владелец файла' (который находится там, где вы положили код для метода делегата - textFieldShouldReturn). Прелесть этого дизайна в том, что теперь вы можете просто щелкнуть правой кнопкой мыши на любом textField и назначить выход nextField следующему SOTextField объекту, которым вы хотите быть следующим респондентом.

Assigning nextField in IB

Более того, вы можете делать классные вещи, такие как цикл textFields, чтобы после того, как последний потерял фокус, первый снова получит фокус.

Это можно легко расширить, чтобы автоматически назначать returnKeyType из SOTextField для UIReturnKeyNext, если назначено следующее поле - одну ручную настройку меньше.

78 голосов
/ 01 марта 2014

Вот один без делегирования:

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

Работает с использованием (в основном неизвестно) UIControlEventEditingDidEndOnExit UITextField действия.

Вы также можете легко подключить это в раскадровке, поэтому не требуется код или .

Редактировать: на самом деле я не могу понять, как подключить это в раскадровке. becomeFirstResponder не кажется предложенным действием для этого контрольного события, что очень жаль. Тем не менее, вы можете подключить все ваши текстовые поля к одному действию в вашем ViewController, который затем определяет, какой textField к becomeFirstResponder на основе отправителя (хотя тогда это не так элегантно, как приведенное выше программное решение, поэтому IMO делает это с помощью приведенного выше кода в viewDidLoad).

78 голосов
/ 01 декабря 2011

Вот мое решение этой проблемы.

Чтобы решить эту проблему (и потому что я ненавижу полагаться на теги, чтобы делать что-то), я решил добавить пользовательское свойство в объект 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, каждый из которых знает, кто следующий в строке.

Надеюсь, это поможет.

45 голосов
/ 09 марта 2016

Быстрое расширение, которое применяет ответ mxcl, чтобы сделать это особенно легко (адаптировано для swift 2.3 от 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)
    }
}

Это просто в использовании:

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

Расширение установит для кнопки возврата значение «Далее» для всех полей, кроме последнего, и значение «Готово» для последнего поля, а также при переключении фокуса / отклонении клавиатуры.

Swift <2,3 </strong>

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)
        }
    }
25 голосов
/ 29 июня 2015

Более последовательный и надежный способ - использовать NextResponderTextField Вы можете полностью сконфигурировать его из конструктора интерфейса без необходимости устанавливать делегата или использовать view.tag.

Все, что вам нужно сделать, это

  1. Установите тип класса вашего UITextField равным NextResponderTextField enter image description here
  2. Затем установите выход nextResponderField так, чтобы он указывал на следующего респондента, это может быть любой UITextField или любой подкласс UIResponder. Это может быть также UIButton, и библиотека достаточно умна, чтобы вызывать событие TouchUpInside кнопки, только если она включена. enter image description here enter image description here

Вот библиотека в действии:

enter image description here

14 голосов
/ 31 мая 2012

Мне нравятся ОО-решения, которые уже были предложены Anth0 и Answerbot. Однако я работал над быстрым и маленьким POC, поэтому я не хотел загромождать вещи подклассами и категориями.

Другим простым решением является создание NSArray полей и поиск следующего поля при нажатии кнопки next. Не оригинальное решение, но быстрое, простое и легкое в реализации. Кроме того, вы можете увидеть и изменить порядок с первого взгляда.

Вот мой код (основанный на других ответах в этой теме):

@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;
}
  • Я всегда возвращаю НЕТ, потому что не хочу вставлять разрыв строки. Просто подумал, что я укажу на это, поскольку, когда я верну YES, он автоматически выйдет из последующих полей или вставит разрыв строки в моем TextView. Мне понадобилось немного времени, чтобы понять это.
  • activeField отслеживает активное поле в случае, если необходима прокрутка для удаления поля с клавиатуры. Если у вас есть похожий код, убедитесь, что вы присвоили activeField перед сменой первого респондента Смена первого респондента происходит немедленно и немедленно запускает событие KeyboardWasShown.
11 голосов
/ 18 июня 2014

Вот реализация табуляции с использованием категории в UIControl. Это решение обладает всеми преимуществами методов от Michael и Anth0, но работает для всех UIControls, а не только для UITextField s. Он также легко работает с Interface Builder и раскадровками.

Источник и пример приложения: GitHub репозиторий для 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
10 голосов
/ 22 марта 2017

Я перепробовал много кодов и, наконец, это сработало для меня в Swift 3.0 Latest [March 2017]

Класс ViewController должен наследоваться от UITextFieldDelegate, чтобы этот код работал.

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
}

В приведенном выше коде, returnKeyType = UIReturnKeyType.next, где клавиша возврата клавиатуры будет отображаться как Next, у вас также есть другие опции, такие как Join/Go и т. Д., В зависимости от того, как ваше приложение изменяет значения.

Этот textFieldShouldReturn является методом, управляемым UITextFieldDelegate, и здесь у нас есть следующий выбор поля на основе увеличения значения тега

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
 }
9 голосов
/ 03 августа 2012
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...