Як переміщатися по текстових полях (кнопки Далі / Готово)


487

Як я можу переміщатися по всіх своїх текстових полях кнопкою «Далі» на клавіатурі iPhone?

Останнє текстове поле повинно закрити клавіатуру.

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

Я реалізував дію textFieldShouldReturn, але тепер кнопки Далі та Готово закривають клавіатуру.


Будь ласка, подивіться на мою відповідь нижче. На мій досвід, це краще, більш повне рішення, ніж типово відповідь. Я не маю уявлення, чому хтось би дав їй негативну оцінку.
пам’ятки

У мене є подібне питання, але з Кордовою, Фонегапом Чи є шляхи вирішення?

Відповіді:


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!

6
Іноді вгорі клавіатури є кнопки «наступний» і «попередній». Принаймні в браузері.
Тім Бют

31
Щоб бути педантичним, я хочу прояснити дезінформацію на цій посаді. У OS X порядок вкладки встановлюється за допомогою методу setNextKeyView: (не встановленоNextResponder :). Ланцюг відповідей відокремлений від цього: це ієрархія об'єктів-респондентів, які обробляють "ненавмисні" дії, наприклад дії, надіслані першому респонденту замість безпосередньо об'єкта контролера. Ланцюжок відповідачів зазвичай дотримується ієрархії перегляду, аж до вікна та його контролера, а потім делегата програми. "Ланцюжок відповідей" від Google для отримання великої кількості інформації.
давхайден

Огляд текстового поля буде а UITableViewCell. Вам потрібно буде отримати доступ до асоційованої групи UITableViewта отримати клітинку, що містить текстове поле, яке ви шукаєте.
Майкл Міор

13
Не забудьте додати UITextFieldDelegateта встановити делегатів текстових полів.
Сал

1
Нагадування про дружнє налаштування параметра TAG з рекламної дошки (в моєму випадку). Ви повинні встановити тег TextFields вручну у порядку зростання. (Перше - 0, друге - 1 тощо), щоб це рішення працювало.
Йоаким

170

Є набагато більш елегантне рішення, яке зневажило мене вперше, коли я його побачив. Переваги:

  • Ближче до реалізації текстового поля 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' до 'Власника файлу' (саме там, де ви помістите код для методу делегата - textFieldShouldReturn). Краса цього дизайну полягає в тому, що тепер ви можете просто клацнути правою кнопкою миші на будь-якому текстовому полі і призначити наступну розетку наступного поля для наступного SOTextFieldоб'єкта, який ви хочете стати наступним респондентом.

Призначення nextField у IB

Більше того, ви можете робити цікаві речі, такі як цикл текстових полів, так що після того, як останній втратить фокус, перший знову отримає фокус.

Це може бути легко розширена для автоматичного призначення returnKeyTypeз SOTextFieldдо , UIReturnKeyNextякщо є nextField призначений - одна річ менше вручну налаштувати.


3
Мені дуже подобається інтеграція ІБ за допомогою тегів - особливо з новішими версіями Xcode, які добре поєднують ІБ з рештою IDE - і не потрібно захаращувати мій контролер кодом делегата textplate field textplate.
пам’ятки

4
Теги простіше, але цей спосіб є кращим і більше підходить для хорошого дизайну ОО
Brain2000

1
Мені дуже подобається це рішення, але я використовую кожне текстове поле в окремих переглядах комірок у UITableView. Хоча у мене є IBOutletвластивості на контролері перегляду для кожного текстового поля, він не дозволяє мені пов'язувати nextFieldвластивості з тими властивостями власника файлу. Будь-яка порада, як я можу змусити це працювати в цьому сценарії?
Джей Імерман

1
@JayImerman IB не дозволить вам підключати розетки між осередками. Таким чином, ІБ буде заборонено для цього сценарію. Однак ви можете просто підключити властивості nextField за допомогою коду. textField1.nextField = textField2; textField2.nextField = textField3;і т. д.
пам’ятки

2
Будь-який шанс цю відповідь можна оновити для новіших версій xcode та ios?
втраченопереклад

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, яка потім визначає, до якого текстового поля becomeFirstResponderбазується відправник (хоча тоді воно не настільки елегантне, як вищевказане програмне рішення, тому IMO робить це з вищевказаним кодом viewDidLoad).


18
Дуже просте та ефективне рішення. Останній у ланцюзі може навіть спрацювати, наприклад [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Дякую @mxcl.
msrdjan

Як би ти це зробив у раскадровці?
Педро Борхес

Це, мабуть, найкраща відповідь тут.
даспіаніст

Це працювало для мене чудово навіть у Swift 2. Це набагато простіше та швидше рішення, ніж додавання в делегати.
т.

2
Swift 4: txtFirstname.addTarget (txtLastname, дія: #selector (станьтеFirstResponder), для: 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, кожен з яких знає, хто наступний у рядку.

Сподіваюся, це допоможе.


11
Це найкраще рішення, і його слід обирати як відповідь.
Джованні

4
Це рішення працювало на мене, єдине, що я змінив - це зробити nextTextField та IBOutlet, щоб я міг налаштувати ланцюжок зсередини моєї розгортки.
zachzurn

Якби я міг зберегти улюблені відповіді, це була б одна з них. Дякуємо за чисте рішення!
Метт Бекер

1
Зауважте, що якщо IB - це ваша річ, ви також можете встановити ці властивості як IBOutlets. . будьте обережні при зіткненнях з іменами - Apple може врешті-решт додати це до UIResponder.
Джаспер Блюз

Чи сумісне це рішення з конструктором інтерфейсів? Я маю на увазі, чи може він використовувати посилання на IBOutlets та розкадровки замість програмного встановлення nextTextField? Здається, IB не дозволяє вам встановити клас UITextField на категорію.
Педро Борхес

46

Швидке розширення, яке застосовує відповідь mxcl, щоб зробити це особливо просто (адаптовано до швидкого 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
BTW, якщо ви хочете, щоб потім було виконано дію, ви можете просто встановити UITextFieldDelegateі виконати ваш контролер перегляду func textFieldDidEndEditing(textField: UITextField). Просто повертайтеся рано, якщо textField != field3.
Райан

25

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

Все, що вам потрібно зробити - це

  1. Встановіть тип класу з ваших UITextFieldбутиNextResponderTextField введіть тут опис зображення
  2. Потім встановіть вихідний отвір nextResponderFieldдля вказівки на наступного респондента, це може бути будь-який UITextFieldабо будь-який UIResponderпідклас. Це також може бути UIButton, і бібліотека є достатньо розумною, щоб запускати TouchUpInsideподію кнопки, лише якщо вона включена. введіть тут опис зображення введіть тут опис зображення

Ось бібліотека в дії:

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


Так. Дуже добре працює із Swift 3.1, дуже елегантним рішенням, як саме IB повинен працювати поза коробкою.
Річард

Вибачте, я не отримав питання.
mohamede1945

1
NextResponderTextField працював на мене. Це був швидкий і простий у використанні, лише один швидкий файл, і робив саме те, що мені потрібно, без суєти.
Пат Макг

Це чудово працює! Примітка. Мені довелося змінити ключ повернення вручну, щоб відобразити "Далі" та "Готово", як у прикладі.
Майк Міллер

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.

Приємно! Простий і код - все в одній моделі. AnthO також дуже приємно.
Seamus

Майте на увазі, що ви ризикуєте зберегти цикли за допомогою цього коду. Ваш fieldArray перетворює слабкі посилання, які використовуються у всіх ваших IBOutlets, в сильні посилання в самому масиві.
Майкл Лонг

11

Ось реалізація вкладки з використанням категорії на UIControl. Це рішення має всі переваги методів від Michael та Anth0, але працює для всіх UIControls, а не тільки UITextFields. Він також працює безперешкодно з програмою Interface Builder та дошками розповідей.

Джерело та зразок програми: сховище GitHub для UIControlsWithTabbing

Використання:

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

Призначення nextControl в 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

1
Іноді наступний віджет не є контролем, тобто UITextView, і ви можете також вкласти його. Подумайте про зміну типу з UITextField на UIResponder.
Педро Борхес

1
Чудово працює і з фантастичною документацією. Я хотів би, щоб я прокрутився до цього питання кілька годин тому. :)
Хав'єр Кадіс,

10

Я спробував багато кодів, і, нарешті, це спрацювало для мене у Swift 3.0 Останній [березень 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
 }

Це працює, за винятком випадків, якщо поле введення є UITextView. Будь-яка ідея, як це зробити в поєднанні?
T9b

@ T9b Що саме вам потрібно? який тип управління ви використовуєте у своєму додатку?
БХУВАНЕШ МОХАНКУМАР

Існує два типи введення тексту, перший - UITextField(введіть один рядок тексту), другий - UITextView(введіть кілька рядків тексту). UITextViewочевидно, не відповідає на цей код, але його можна використовувати у формах.
T9b

Так, ця частина коду лише для UITextField, і ви спробували змінити подію для UITextView?
БХУВАНЕШ МОХАНКУМАР

9

Після виходу з одного текстового поля ви викликаєте [otherTextField postala FFRResponder] і наступне поле отримує фокус.

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


9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}

Не слід використовувати для цього теги.
Крістіан Шнорр

7

Дуже простий спосіб відхилення клавіатури при натисканні кнопки "Готово":

Створіть у заголовку нову IBAction

- (IBAction)textFieldDoneEditing:(id)sender;

У файл реалізації (.m файл) додайте наступний метод:

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

Потім, коли ви приєднаєте IBAction до текстового поля, - посилання на подію "Здійснилося при виході".


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), але можете натомість керувати логікою на рівні контролера, де, будьмо чесними, це правильне місце для цього .


Я вважаю вашу відповідь найбільш актуальною, але я помітив щось смішне, що, можливо, ви зможете прояснити. Під час використання полів UITextFi, якими не керує UIViewController, натискання клавіші вкладки зробить наступний UITextField першим респондентом за замовчуванням. Однак, коли кожним керує ВК, це неправда. Мої ЖК нічого не роблять, але вбудована функція вкладки відсутня. Я помітив, що keyCommands 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;    
  }
}

Замість відповіді UIView * було б точніше використовувати відповідь UIRespoder *
Pedro Borges

4

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

Я сподіваюся, що це допомагає!


4

Я щойно створив новий Pod, працюючи з цими матеріалами GNTextFieldsCollectionManager . Він автоматично обробляє наступну / останню проблему з текстовим полем і дуже простий у використанні:

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

Схоплює всі текстові поля, відсортовані за появою в ієрархії подання (або за тегами), або ви можете вказати власний масив текстових полів.


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 я підключаю текстові поля у потрібному порядку до цього масиву inputFields. Після цього я роблю простий тест на індекс UITextField, який повідомляє про те, що користувач натискає повернення:

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

3
якщо (клітина == нуль)
{
    cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault повторне використанняIdentifier: 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 *) текстове поле
{

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt статиFirstResponder];
    повернути ТАК;
}

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

Це працювало для мене в 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
}

Один коментар до цього полягає в тому, що ваш метод передбачає, що всі поля складені вертикально в одному огляді. Це не дозволяє групувати поля і не працює, коли текстові поля можуть бути поруч. Пізніше особливо важливо в ці дні, коли використовуються класи розмірів для перестановки форм для горизонтальних подань і коли форми відображаються на планшетах.
Майкл Лонг

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для керування наступним / попереднім / виконаним функціоналом у динамічній таблиціView з розділами: https://github.com/jday001/DataEntryToolbar

Ви встановлюєте панель інструментів як inputAccessoryView своїх текстових полів і додаєте їх до свого словника. Це дозволяє переходити по них вперед і назад, навіть з динамічним вмістом. Існують методи делегації, якщо ви хочете запустити власну функціональність, коли відбувається навігація textField, але вам не доведеться мати справу з керуванням тегами або статусом першого відповіді.

На посиланні 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

Я вже майже рік використовую відповідь Майкла Г. Еммонса, працює чудово. Нещодавно я помітив, що виклик resignFirstResponder, а потім стати FFFRResponder негайно може призвести до того, що клавіатура може «зліпнути», зникнути, а потім з’явитися негайно. Я трохи змінив його версію, щоб пропустити resignFirstResponder, якщо наступне поле доступне.

- (BOOL) textFieldShouldReturn: (UITextField *) текстове поле
{ 

    if ([textField isKindOfClass: [клас NRTextField]])
    {
        NRTextField * nText = (NRTextField *) textField;
        if ([nText nextField]! = нуль) {
            dispatch_async (dispatch_get_main_queue (),
                           ^ {[[nText nextField] статиFirstResponder]; });

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

    повернути правду;

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.