Как перемещаться по текстовым полям (кнопки «Далее» / «Готово»)

487

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

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

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

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

PHX
источник
Пожалуйста, посмотрите на мой ответ ниже. По моему опыту, это лучшее, более полное решение, чем обычно дается ответ. Я понятия не имею, почему кто-то дал бы ему отрицательный рейтинг.
мемоны
У меня похожая проблема, но с Cordova, Phonegap Есть ли способы ее решить?

Ответы:

578

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

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

  1. Все «вкладки» UITextFieldнаходятся в одном родительском представлении.
  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!
PeyloW
источник
6
Иногда на клавиатуре располагаются кнопки «Далее» и «Предыдущий». По крайней мере, в браузере.
Тим Бют
31
Просто чтобы быть педантичным, я хочу прояснить некоторую дезинформацию в этом посте. В OS X порядок вкладок устанавливается с помощью метода setNextKeyView: (не setNextResponder :) в NSView. Цепочка респондента отделена от этого: это иерархия объектов респондента, которые обрабатывают «нецелевые» действия, например, действия, отправляемые первому респонденту, а не непосредственно объекту контроллера. Цепочка респондента обычно следует иерархии представления вплоть до окна и его контроллера, а затем делегата приложения. Google "цепочка респондента" для большого количества информации.
davehayden
Superview текстового поля будет UITableViewCell. Вам нужно будет получить доступ к связанному UITableViewи получить ячейку, содержащую текстовое поле, которое вы ищете.
Майкл Миор
13
Не забудьте добавить UITextFieldDelegateи установить текстовые поля делегатов.
Сал
1
Дружеское напоминание, чтобы установить опцию TAG из раскадровки (в моем случае). Вы должны вручную установить тег TextFields в порядке возрастания. (Первое - 0, второе - 1 и т. Д.), Чтобы это решение работало.
Иоаким
170

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

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

Назначение nextField в IB

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

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

memmons
источник
3
Мне очень нравится интеграция IB с использованием тегов - особенно с более новыми версиями Xcode, который прекрасно интегрирует IB с остальной частью IDE - и отсутствие необходимости загромождать мой контроллер шаблонным кодом делегата textField является бонусом.
меммоны
4
Теги проще, но этот способ лучше и больше подходит для хорошего дизайна ОО
Brain2000
1
Мне действительно нравится это решение, но я использую каждое текстовое поле в отдельных видах ячеек в UITableView. Хотя у меня есть IBOutletсвойства в контроллере представления для каждого текстового поля, он не позволяет мне связать nextFieldсвойство с этими свойствами в владельце файла. Любой совет, как я могу заставить это работать в этом сценарии?
Джей Имерман
1
@JayImerman IB не позволит вам подключать розетки между ячейками. Таким образом, IB будет не по этому сценарию. Однако вы можете просто подключить свойства nextField с помощью кода. textField1.nextField = textField2; textField2.nextField = textField3;и т. д.
меммоны
2
Есть ли шанс, что этот ответ может быть обновлен для более новых версий xcode и ios?
lostintranslation
82

Вот один без делегации:

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

mxcl
источник
18
Очень простое и эффективное решение. Последний в цепи может даже вызвать, например [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Спасибо @mxcl.
msrdjan
Как бы вы сделали это в раскадровке?
Педро Борхес
Это, вероятно, лучший ответ здесь.
деспианист
Это отлично работает даже в Swift 2. Это гораздо более простое и быстрое решение, чем добавление делегатов.
тонна
2
Swift 4: txtFirstname.addTarget (txtLastname, action: #selector (intoFirstResponder), для: UIControlEvents.editingDidEndOnExit)
realtimez
78

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

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

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

Anth0
источник
11
Это лучшее решение и должно быть выбрано в качестве ответа.
Джованни
4
Это решение работало для меня, единственное, что я изменил, - это создание nextTextField и IBOutlet, чтобы я мог настроить цепочку внутри своего раскадровки.
'23
Если бы я мог сохранить любимые ответы, это был бы один из них. Спасибо за чистое решение!
Мэтт Беккер
1
Обратите внимание, что если IB - ваша вещь, вы также можете установить эти свойства как IBOutlets. , будьте осторожны с конфликтами имен - Apple может в конечном итоге добавить это в UIResponder.
Джаспер Блюз
Совместимо ли это решение с конструктором интерфейсов? Я имею в виду, может ли он использовать ссылки IBOutlets и раскадровки вместо программной установки nextTextField? IB, кажется, не позволяет вам устанавливать класс UITextField в категорию.
Педро Борхес
46

Быстрое расширение, которое применяет ответ 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])

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

Свифт <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)
        }
    }
Амос Джошуа
источник
5
Кстати, если вы хотите, чтобы Done выполнял какое-то действие, вы можете просто настроить View Controller UITextFieldDelegateи реализовать его func textFieldDidEndEditing(textField: UITextField). Просто вернись рано, если textField != field3.
Райан
25

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

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

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

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

введите описание изображения здесь

mohamede1945
источник
Да. Очень хорошо работает с Swift 3.1, очень элегантным решением, как IB должен работать из коробки.
Ричард
Извините, я не получил вопрос.
mohamede1945
1
NextResponderTextField работал для меня. Он был быстрым и простым в использовании, всего один быстрый файл, и делал то, что мне нужно, без суеты.
Пэт
Это прекрасно работает! Примечание: мне пришлось изменить ключ Return вручную, чтобы отобразить «Next» и «Done», как в примере.
Майк Миллер
14

Мне нравятся ОО-решения, которые уже были предложены 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.
шатер
источник
Ницца! Простой и код все в одной модели. AnthO тоже очень хороший.
Симус
Имейте в виду, что вы рискуете сохранить циклы с этим кодом. Ваш fieldArray преобразует слабые ссылки, используемые во всех ваших IBOutlets, в сильные ссылки в самом массиве.
Майкл Лонг
11

Вот реализация вкладок с использованием категории на UIControl. Это решение обладает всеми преимуществами методов от Michael и Anth0, но работает для всех UIControls, а не только для UITextFields. Это также работает без проблем с Интерфейсным Разработчиком и раскадровками.

Источник и пример приложения: репозиторий GitHub для UIControlsWithTabbing

Применение:

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

Назначение nextControl в Интерфейсном Разработчике

Заголовок:

//
// 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
Picciano
источник
1
Иногда следующий виджет не является элементом управления, т. Е. UITextView, и вы также можете использовать вкладку там. Попробуйте изменить тип с UITextField на UIResponder.
Педро Борхес
1
Прекрасно работает и с фантастической документацией. Я хотел бы прокрутить этот вопрос несколько часов назад. :)
Хавьер Кадис
10

Я перепробовал много кодов и, наконец, это сработало для меня в 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.nextwhere заставит клавишу «Клавиатура» вернуться к отображению, поскольку у 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
 }
БХУВАНЕШ МОХАНКУМАР
источник
Это работает, за исключением случаев, когда поле ввода UITextView. Есть идеи, как это сделать в комбинации?
T9b
@ T9b Что тебе нужно именно? какой тип управления вы используете в своем приложении?
БХУВАНЕШ МОХАНКУМАР
Существует два типа ввода текста: первый - UITextField(введите одну строку текста), другой - UITextView(введите несколько строк текста). UITextViewочевидно, не отвечает на этот код, но его можно использовать в формах.
T9b
Да, эта часть кода только для UITextField, и вы пытались изменить событие для UITextView?
БХУВАНЕШ МОХАНКУМАР
9

После выхода из одного текстового поля вы вызываете [otherTextField intoFirstResponder], и следующее поле получает фокус.

Это на самом деле может быть сложной задачей, так как часто вам также нужно прокручивать экран или иным образом регулировать положение текстового поля, чтобы его было легко увидеть при редактировании. Просто убедитесь, что много тестировались, входя и выходя из текстовых полей по-разному, а также уходя рано (всегда дайте пользователю возможность отклонить клавиатуру вместо перехода к следующему полю, обычно с помощью кнопки «Готово» в навигационная панель)

Кендалл Хельмштеттер Гелнер
источник
9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
iKushal
источник
Вы не должны использовать теги для этой цели.
Кристиан Шнорр
7

Очень простой способ отменить клавиатуру при нажатии кнопки «Готово»:

Создайте новый IBAction в шапке

- (IBAction)textFieldDoneEditing:(id)sender;

В файле реализации (файл .m) добавьте следующий метод:

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

Затем, когда вы пришли, чтобы связать IBAction с текстовым полем - ссылка на событие «Закончено ли при выходе».

jcrowson
источник
1
Пожалуйста, он не перемещается по текстовым полям, но закрывает клавиатуру и позволяет выбрать другое поле.
jcrowson
7

Сначала установите клавишу возврата клавиатуры в 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;
}
Махеш чаудары
источник
7

Меня удивляет, как много ответов здесь не в состоянии понять одну простую концепцию: навигация по элементам управления в вашем приложении - это не то, что сами представления должны делать. Задача контроллера - решить, какой элемент управления сделать следующим первым респондентом.

Также большинство ответов относится только к навигации вперед, но пользователи также могут захотеть вернуться назад.

Итак, вот что я придумала. Ваша форма должна управляться контроллером представления, а контроллеры представления являются частью цепочки респондента. Таким образом, вы можете свободно использовать следующие методы:

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

Кристиан Шнорр
источник
Я считаю ваш ответ наиболее актуальным, но я заметил кое-что забавное, что, возможно, вы можете прояснить. При использовании UITextFields, которые не управляются UIViewController, нажатие клавиши табуляции сделает следующий UITextField первым респондентом по умолчанию. Однако, когда каждый управляется VC, это не так. Мои ВК ничего не делают, но встроенная функциональность вкладок исчезла. Я заметил, что ключевые команды VC заканчивают тем, что были добавлены к цепочке респондента представления. Это наводит меня на мысль, что если вы используете VC, то обязательно нужно реализовывать keyCommands.
Джои Карсон
4

Я добавил к ответу 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
Джеймс Мерц
источник
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;    
  }
}
rithik
источник
Вместо респондента UIView * было бы точнее использовать респондента UIRespoder *
Педро Борхес
4

Я попытался решить эту проблему, используя более сложный подход, основанный на назначении каждой ячейке (или UITextField) UITableViewуникального значения тега, которое можно впоследствии извлечь: activ-next-uitextfield-in-uitableview-ios

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

Фабиано Франческони
источник
4

Я только что создал новый Pod при работе с этим материалом GNTextFieldsCollectionManager . Он автоматически обрабатывает следующую / последнюю проблему textField и очень прост в использовании:

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

Захватывает все текстовые поля, отсортированные по иерархии представления (или по тегам), или вы можете указать свой собственный массив textFields.

JakubKnejzlik
источник
4

Решение в 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
        }

    }
Кришна Тхакур
источник
4

Если кто-то хочет так. Я думаю, что это наиболее близко к требованиям, о которых идет речь

введите описание изображения здесь

Вот как я это реализовал

Добавьте вспомогательный вид для каждого текстового поля, для которого вы хотите настроить, используя

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 в последовательности, в которой вы хотите, чтобы реагировала следующая кнопка / prev.

Мохаммад Садик
источник
3

Я предпочитаю:

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

В файле NIB я подключаю textFields в нужном порядке к этому массиву inputFields. После этого я делаю простой тест для индекса UITextField, который сообщает, что пользователь нажал return:

// 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
источник
3
если (ячейка == ноль)
{
    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]; // Инициализируем изменяемый массив в ViewDidLoad
}

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

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt intoFirstResponder];
    вернуть ДА;
}
Ребекка Керти
источник
3

У меня было около 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
Незаконный аргумент
источник
3

Это сработало для меня в 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 нет тега, установите его сейчас:

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.
Даниэль Д.
источник
3

Это простое решение в быстром, без использования тегов, без уловок раскадровки ...

Просто используйте это расширение:

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
}
Кевин ABRIOUX
источник
Один комментарий по этому поводу заключается в том, что ваш метод предполагает, что все поля расположены вертикально в одном суперпредставлении. Он не учитывает сгруппированные поля и не работает, когда текстовые поля могут располагаться рядом. Последнее особенно важно в наши дни, когда используются классы размеров для перестановки форм для горизонтальных видов и когда формы отображаются на планшетах.
Майкл Лонг
3

Более безопасный и прямой путь, предполагая:

  • делегаты текстового поля установлены на вашем контроллере представления
  • все текстовые поля являются подпредставлениями одного и того же представления
  • текстовые поля имеют теги в том порядке, в котором вы хотите их выполнить (например, textField2.tag = 2, textField3.tag = 3 и т. д.)
  • переход к следующему текстовому полю произойдет, когда вы нажмете кнопку возврата на клавиатуре (вы можете изменить это на следующее , готово и т. д.)
  • Вы хотите, чтобы клавиатура отклоняла после последнего текстового поля

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

    }
}
Джейсон З
источник
2

в textFieldShouldReturn вы должны проверить, что текстовое поле, в котором вы находитесь в данный момент, не является последним, когда они щелкают далее, и если оно не отменяет клавиатуру.

Даниил
источник
Эй, хорошо, это работает, но как я могу перейти к следующему текстовому полю с помощью кнопки «Далее»?
PHX
2

Это старый пост, но у него высокий рейтинг страниц, поэтому я поделюсь своим решением.

У меня была похожая проблема, и в итоге я создал подкласс UIToolbarдля управления следующей / предыдущей / готовой функциональностью в динамическом табличном представлении с разделами: https://github.com/jday001/DataEntryToolbar

Вы устанавливаете панель инструментов как inputAccessoryView ваших текстовых полей и добавляете их в свой словарь. Это позволяет вам переключаться между ними вперед и назад, даже с динамическим контентом. Существуют методы делегирования, если вы хотите запускать свои собственные функции, когда происходит навигация по textField, но вам не приходится иметь дело с управлением какими-либо тегами или статусом первого респондента.

По ссылке на GitHub есть фрагменты кода и пример приложения, чтобы помочь с деталями реализации. Вам понадобится ваша собственная модель данных, чтобы отслеживать значения внутри полей.

jday
источник
2

Без использования тегов и без добавления свойства для 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];
                                                    }];
Кер
источник
2

Я использую ответ Майкла Дж. Эммонса уже около года, прекрасно работает. Недавно я заметил, что вызов resignFirstResponder и затем сталFirstResponder немедленно может привести к "сбоям" клавиатуры, исчезновению и немедленному появлению. Я немного изменил его версию, чтобы пропустить resignFirstResponder, если nextField доступен.

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

        }
        еще {
            [textField resignFirstResponder];
        }
    }
    еще {
        [textField resignFirstResponder];
    }

    вернуть истину;

}
arinmorf
источник