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

415

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

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

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

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

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

34 ответа

502
Лучший ответ

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

Это можно легко сделать в любом случае с двумя предположениями:

  • Все "tabbable" UITextField находятся на одном и том же родительском представлении.
  • Их "порядок табуляции" определяется свойством тега.

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

func textFieldShouldReturn(textField: UITextField) -> Bool {

let nextTage=textField.tag+1;
// Try to find next responder
let nextResponder=textField.superview?.viewWithTag(nextTage) as UIResponder!

if (nextResponder != nil){
    // Found next responder, so set it.
    nextResponder?.becomeFirstResponder()
}
else
{
    // Not found, so remove keyboard
    textField.resignFirstResponder()
}
return false // We do not want UITextField to insert line-breaks.
}

Если надписью текстового поля будет UITableViewCell, тогда следующий ответчик будет

let nextResponder=textField.superview?.superview?.superview?.viewWithTag(nextTage) as UIResponder!
  • 6
    Иногда на клавиатуре есть кнопки «Далее» и «Предыдущий». По крайней мере, в браузере.
  • 30
    Просто чтобы быть педантичным, я хочу прояснить некоторую дезинформацию в этом посте. В OS X порядок вкладок устанавливается с помощью метода setNextKeyView: (не setNextResponder :) в NSView. Цепочка респондента отделена от этого: это иерархия объектов респондента, которые обрабатывают «нецелевые» действия, например, действия, отправляемые первому респонденту, а не непосредственно объекту контроллера. Цепочка респондента обычно следует иерархии представления, вплоть до окна и его контроллера, а затем делегата приложения. Google "цепочка респондента" для большого количества информации.
Показать ещё 11 комментариев
157

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

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

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

Изображение 5355

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

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

  • 0
    Я думаю, что установить теги проще, чем использовать этот подкласс. Но это интересная альтернатива ... спасибо
  • 2
    Мне действительно нравится интеграция IB с использованием тегов - особенно с более новыми версиями Xcode, который прекрасно интегрирует IB с остальной частью IDE - и отсутствие необходимости загромождать мой контроллер шаблонным кодом делегата textField является бонусом.
Показать ещё 9 комментариев
77

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

Чтобы решить эту проблему (и потому, что я ненавижу полагаться на теги, чтобы делать вещи), я решил добавить настраиваемое свойство к объекту 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
    Это лучшее решение и должно быть выбрано в качестве ответа.
  • 4
    Это решение работало для меня, единственное, что я изменил, - это создание nextTextField и IBOutlet, чтобы я мог настроить цепочку внутри своего раскадровки.
Показать ещё 7 комментариев
66

Здесь один без делегации:

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

  • 18
    Очень простое и эффективное решение. Последний в цепочке может даже вызвать, например: [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit]; , Спасибо @mxcl.
  • 7
    Очень хороший ответ, суета бесплатно и надежно
Показать ещё 10 комментариев
41

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

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 выполнял UITextFieldDelegate либо действие, вы можете просто заставить свой View Controller соответствовать UITextFieldDelegate и реализовать func textFieldDidEndEditing(textField: UITextField) . Просто вернитесь рано, если textField != field3 .
  • 0
    Большое спасибо за код Swift 3
22

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

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

  • Задайте тип класса вашего UITextField как NextResponderTextField Изображение 5356
  • Затем установите розетку nextResponderField, чтобы указать на следующий ответчик, это может быть любой UITextField или любой подкласс UIResponder. Это может быть также UIButton, и библиотека достаточно умна, чтобы запускать событие TouchUpInside кнопки, только если она включена. Изображение 5357Изображение 5358

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

Изображение 5359

  • 0
    Очень хорошо, но работает ли это с xcode 8? Свифт 3.
  • 0
    Да. Очень хорошо работает с Swift 3.1, очень элегантным решением, как IB должен работать из коробки.
Показать ещё 3 комментария
14

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

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

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

@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;
}
  • Я всегда возвращаю НЕТ, потому что я не хочу, чтобы был вставлен разрыв строки. Просто подумал, что я укажу, что с тех пор, как я вернусь, он автоматически выйдет из последующих полей или введет разрыв строки в моем TextView. Мне потребовалось немного времени, чтобы понять это.
  • activeField отслеживает активное поле в случае необходимости прокрутки, чтобы освободить поле от клавиатуры. Если у вас есть аналогичный код, убедитесь, что вы назначили activeField, прежде чем менять первого ответчика. Изменение первого ответчика является немедленным и немедленно вызовет событие KeyboardWasShown.
  • 0
    Ницца! Простой и код все в одной модели. AnthO тоже очень хороший.
  • 0
    Имейте в виду, что вы рискуете сохранить циклы с этим кодом. Ваш fieldArray преобразует слабые ссылки, используемые во всех ваших IBOutlets, в сильные ссылки в самом массиве.
10

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

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

Использование:

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

Изображение 5360

Заголовок:

//
// 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
    Иногда следующий виджет не является элементом управления, т. Е. UITextView, и вы также можете использовать вкладку там. Попробуйте изменить тип с UITextField на UIResponder.
  • 1
    Прекрасно работает и с фантастической документацией. Я хотел бы прокрутить этот вопрос несколько часов назад. :)
Показать ещё 1 комментарий
9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
  • 0
    Вы не должны использовать теги для этой цели.
9

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

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

6

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

Очень простой способ отклонения клавиатуры при нажатии кнопки "Готово":

Создайте новый IBAction в заголовке

- (IBAction)textFieldDoneEditing:(id)sender;

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

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

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

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

Я пробовал много кодов, и, наконец, это сработало для меня в 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, где будет отображаться клавиша возврата клавиши Key, как 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
 }
  • 0
    Это работает, за исключением случаев, когда поле ввода UITextView . Есть идеи, как сделать это в комбинации?
  • 0
    @ T9b Что тебе нужно именно? какой тип управления вы используете в своем приложении?
Показать ещё 2 комментария
5

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

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

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

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

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

Другим преимуществом такого подхода является то, что вам не нужно подклассировать все виды элементов управления, которые вы можете отображать (например, UITextField s), но вместо этого можете управлять логикой на уровне контроллера, где, допустим, подходящее место для этого.

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

Это сработало для меня в 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.
4

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

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

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

4

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

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

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 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;    
  }
}
  • 0
    Вместо респондента UIView * было бы точнее использовать респондента UIRespoder *
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 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
    Один комментарий по этому поводу заключается в том, что ваш метод предполагает, что все поля расположены вертикально в одном суперпредставлении. Он не учитывает сгруппированные поля и не работает, когда текстовые поля могут располагаться рядом. Последнее особенно важно в наши дни, когда используются классы размеров для перестановки форм для горизонтальных видов и когда формы отображаются на планшетах.
3

У меня было около 10+ UITextField в моей истории, и способ, которым я включил следующую функциональность, - создать массив UITextField и сделать следующий UITextField первымResponder. Здесь файл реализации:

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

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

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

В файле NIB я привязываю текстовые поля в нужном порядке к этому массиву 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;
}
2

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

    }
2

Вот версия Anth0 версии Swift 3. Я размещаю его здесь, чтобы помочь быстрым разработчикам, желающим воспользоваться его замечательным ответом! Я взял на себя смелость добавить тип возвращаемого ключа "Следующий", когда вы устанавливаете связанный объект.

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

вы можете использовать библиотеку IQKeyboardManager для этого. он обрабатывает все, вам не нужна дополнительная настройка. IQKeyboardManager доступен через CocoaPods, для его установки просто добавьте следующую строку в ваш подфайл:

pod 'IQKeyboardManager'

или Просто перетащите каталог IQKeyBoardManager из демонстрационного проекта в свой проект. Это. вы можете найти каталог IQKeyBoardManager из https://github.com/hackiftekhar/IQKeyboardManager

2

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

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

}
2

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

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

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

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

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

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

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

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

Изображение 5361

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

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

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

0

Swift 3 Solution, используя упорядоченный массив 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
            }
        }
    }
}
-1

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

Некоторые из его функций 1) ZERO LINE OF CODE

2) Автоматически работает

3) Больше нет UIScrollView

4) Нет дополнительных подклассов

5) Нет дополнительной ручной работы

6) Нет больше #imports

https://github.com/asefnoor/IQKeyboardManager

-2

Swift 4 для ответа mxcl:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню