Jak poruszać się po polach tekstowych (przyciski Dalej / Gotowe)

487

Jak poruszać się po wszystkich polach tekstowych za pomocą przycisku „Dalej” na klawiaturze iPhone'a?

Ostatnie pole tekstowe powinno zamknąć klawiaturę.

Mam ustawione przyciski IB (Dalej / Gotowe), ale teraz utknąłem.

Zaimplementowałem akcję textFieldShouldReturn, ale teraz przyciski Dalej i Gotowe zamykają klawiaturę.

phx
źródło
Proszę spojrzeć na moją odpowiedź poniżej. Z mojego doświadczenia wynika, że ​​jest to lepsze, bardziej kompletne rozwiązanie niż zazwyczaj udzielana odpowiedź. Nie mam pojęcia, dlaczego ktokolwiek dałby mu negatywną ocenę.
memmons
Mam podobny problem, ale z Cordova, Phonegap Czy są jakieś sposoby na rozwiązanie tego problemu?

Odpowiedzi:

578

W Cocoa dla Mac OS X masz kolejny łańcuch odpowiedzi, w którym możesz zapytać pole tekstowe, na czym powinna się skupić kontrola. To sprawia, że ​​tabulacje między polami tekstowymi działają. Ale ponieważ urządzenia iOS nie mają klawiatury, tylko dotyk, ta koncepcja nie przetrwała przejścia na Cocoa Touch.

Można to i tak łatwo zrobić, przyjmując dwa założenia:

  1. Wszystkie tabulatory UITextFieldsą w tym samym widoku nadrzędnym.
  2. Ich „kolejność tabulatorów” jest zdefiniowana przez właściwość tagu.

Zakładając, że możesz zastąpić funkcję textFieldShouldReturn: as this:

-(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.
}

Dodaj trochę kodu, a założenia można również zignorować.

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
}

Jeśli nadzorem pola tekstowego będzie UITableViewCell, to następnym odpowiadającym będzie

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder!
PeyloW
źródło
6
Czasami na klawiaturze znajdują się przyciski „następny” i „poprzedni”. Przynajmniej w przeglądarce.
Tim Büthe
31
Żeby być pedantycznym, chcę wyjaśnić pewne dezinformacje w tym poście. W systemie OS X kolejność tabulatorów ustawia się za pomocą metody setNextKeyView NSView: (nie setNextResponder :). Łańcuch odpowiadających jest oddzielny od tego: jest to hierarchia obiektów odpowiadających, które obsługują akcje „bezkierunkowe”, np. Akcje wysyłane do pierwszego respondera zamiast bezpośrednio do obiektu kontrolera. Łańcuch odpowiadający zwykle podąża za hierarchią widoku, aż do okna i kontrolera, a następnie deleguje aplikację. „Łańcuch odpowiedzi” Google zapewnia wiele informacji.
davehayden
Nadzorem pola tekstowego będzie UITableViewCell. Będziesz musiał uzyskać dostęp do powiązanego UITableViewi uzyskać komórkę zawierającą szukane pole tekstowe.
Michael Mior
13
Nie zapomnij dodać UITextFieldDelegatei ustawić delegatów pól tekstowych.
Sal
1
Przyjazne przypomnienie, aby ustawić opcję TAG z serii ujęć (w moim przypadku). Musisz ręcznie ustawić znacznik TextFields w kolejności rosnącej. (Pierwsza to 0, druga to 1 itd.), Aby to rozwiązanie działało.
Joakim
170

Jest o wiele bardziej eleganckie rozwiązanie, które zaskoczyło mnie, gdy go zobaczyłem po raz pierwszy. Korzyści:

  • Bliżej implementacji pola tekstowego OSX, w którym pole tekstowe wie, gdzie należy skupić się dalej
  • Nie polega na ustawianiu lub używaniu tagów - które są IMO kruche w tym przypadku użycia
  • Może być rozszerzony do pracy zarówno z kontrolkami, jak UITextFieldi UITextViewdowolnym kontrolerem interfejsu użytkownika
  • Nie zaśmieca kontrolera widoku kodem delegującym UITextField na płycie wzorcowej
  • Ładnie integruje się z IB i można go skonfigurować za pomocą znanej opcji przeciągnij i upuść, aby podłączyć gniazda.

Utwórz podklasę UITextField, która ma IBOutletwłaściwość o nazwie nextField. Oto nagłówek:

@interface SOTextField : UITextField

@property (weak, nonatomic) IBOutlet UITextField *nextField; 

@end

A oto wdrożenie:

@implementation SOTextField

@end

W kontrolerze widoku utworzysz -textFieldShouldReturn:metodę delegowania:

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

W IB zmień swoje UITextFields, aby użyć SOTextFieldklasy. Następnie, również w IB, ustaw delegata dla każdego z „SOTextFields” do „Właściciela pliku” (co jest dokładnie tam, gdzie umieścisz kod dla metody delegowania - textFieldShouldReturn). Piękno tego projektu polega na tym, że teraz możesz po prostu kliknąć prawym przyciskiem myszy dowolny textField i przypisać ujście nextField do następnego SOTextFieldobiektu, który chcesz być kolejnym responderem.

Przypisywanie nextField w IB

Co więcej, możesz robić fajne rzeczy, takie jak zapętlanie pól tekstowych, aby po tym, jak ostatni straci fokus, pierwszy otrzyma fokus ponownie.

To można łatwo rozszerzyć, aby automatycznie przypisać returnKeyTypez SOTextFieldDo UIReturnKeyNextjeśli istnieje nextField przypisane - jeden mniej rzeczy ręcznie Konfiguruj.

memmons
źródło
3
Naprawdę podoba mi się integracja IB nad używaniem tagów - szczególnie z nowszymi wersjami Xcode, które ładnie integrują IB z resztą IDE - i brak konieczności zaśmiecania mojego kontrolera tekstem typu „pole tekstowe” to bonus.
memmons,
4
Tagi są łatwiejsze, ale ten sposób jest lepszy i bardziej odpowiedni dla dobrego projektu OO
Brain2000
1
Naprawdę podoba mi się to rozwiązanie, ale używam każdego pola tekstowego w osobnych widokach komórek w UITableView. Chociaż mam IBOutletwłaściwości kontrolera widoku dla każdego pola tekstowego, nie pozwala mi to powiązać nextFieldwłaściwości z tymi właściwościami właściciela pliku. Czy jest jakaś rada, jak sprawić, by działała w tym scenariuszu?
Jay Imerman
1
@JayImerman IB nie pozwoli ci połączyć gniazdek między komórkami. Tak więc IB nie będzie pasował do tego scenariusza. Możesz jednak po prostu podłączyć właściwości nextField za pomocą kodu. textField1.nextField = textField2; textField2.nextField = textField3;itd.
Memmons,
2
Czy jest szansa, że ​​odpowiedź ta może zostać zaktualizowana dla nowszych wersji Xcode i iOS?
lostintranslation
82

Oto jeden bez delegacji:

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

Działa przy użyciu (najczęściej nieznanej) UIControlEventEditingDidEndOnExit UITextFieldakcji.

Możesz również łatwo podłączyć to w scenorysie, więc nie jest wymagana delegacja ani kod.

Edycja: w rzeczywistości nie mogę wymyślić, jak podłączyć to w scenorysie. becomeFirstRespondernie wydaje się być oferowaną akcją dla tego zdarzenia kontrolnego, a szkoda. Nadal możesz podpiąć wszystkie pola tekstowe do jednej akcji w ViewController, która następnie określa, który textField ma być becomeFirstResponderoparty na nadawcy (chociaż nie jest tak elegancki jak powyższe rozwiązanie programowe, więc IMO zrobi to z powyższym kodem viewDidLoad).

mxcl
źródło
18
Bardzo proste i skuteczne rozwiązanie. Ostatni w łańcuchu może nawet uruchomić np [tfN addTarget:self action:@selector(loginButtonTapped:) forControlEvents:UIControlEventEditingDidEndOnExit];. Dzięki @mxcl.
msrdjan
Jak zrobiłbyś to w scenorysie?
Pedro Borges
To chyba najlepsza odpowiedź tutaj.
daspianista
Działa to dla mnie doskonale nawet w Swift 2. Jest to o wiele prostsze i szybsze rozwiązanie niż dodawanie delegatów.
ton
2
Szybka 4: txtFirstname.addTarget (txtLastname działanie: #selector (becomeFirstResponder) na: UIControlEvents.editingDidEndOnExit)
realtimez
78

Oto moje rozwiązanie tego problemu.

Aby rozwiązać ten problem (a ponieważ nie lubię polegać na tagach do robienia różnych rzeczy), postanowiłem dodać właściwość niestandardową do obiektu UITextField. Innymi słowy, utworzyłem kategorię na UITextField w następujący sposób:

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

Oto jak go używam:

UITextField *textField1 = ...init your textfield
UITextField *textField2 = ...init your textfield
UITextField *textField3 = ...init your textfield

textField1.nextTextField = textField2;
textField2.nextTextField = textField3;
textField3.nextTextField = nil;

I zaimplementuj metodę textFieldShouldReturn:

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

    UITextField *next = theTextField.nextTextField;
    if (next) {
        [next becomeFirstResponder];
    } else {
        [theTextField resignFirstResponder];
    }

    return NO; 
}

Mam teraz rodzaj powiązanej listy UITextField, z których każdy wie, kto będzie następny w kolejce.

Mam nadzieję, że to pomoże.

Anth0
źródło
11
To najlepsze rozwiązanie i powinno zostać wybrane jako odpowiedź.
Giovanni
4
To rozwiązanie działało dla mnie, jedyną rzeczą, którą zmieniłem, było tworzenie nextTextField i IBOutlet, dzięki czemu mogłem skonfigurować tworzenie łańcuchów z poziomu mojej Storyboard.
zachzurn
Gdybym mógł zapisać ulubione odpowiedzi, byłby to jeden z nich. Dzięki za czyste rozwiązanie!
Matt Becker,
1
Zauważ, że jeśli IB to twoja sprawa, możesz również ustawić te właściwości jako IBOutlety. . bądź ostrożny w przypadku kolizji nazw - Apple może w końcu dodać to do UIResponder.
Jasper Blues,
Czy to rozwiązanie jest kompatybilne z narzędziem do tworzenia interfejsów? Mam na myśli, czy może używać IBOutletów i odniesień do scenorysu zamiast programowo ustawiać nextTextField? Wydaje się, że IB nie pozwala ci ustawić klasy UITextField na kategorię.
Pedro Borges,
46

Szybkie rozszerzenie, które stosuje odpowiedź mxcl, aby uczynić to szczególnie łatwym (dostosowanym do wersji swift 2.3 przez 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)
    }
}

Jest łatwy w użyciu:

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

Rozszerzenie ustawi przycisk powrotu na „Dalej” dla wszystkich pól oprócz ostatniego i na „Gotowe” dla ostatniego pola, a po naciśnięciu klawisza przesuń fokus / wyłącz klawiaturę.

Szybki <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: użyj w ten sposób -

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)
        }
    }
Amos Joshua
źródło
5
BTW, jeśli chcesz, aby Gotowe wykonało akcję, możesz po prostu dostosować swój kontroler widoku UITextFieldDelegatei go wdrożyć func textFieldDidEndEditing(textField: UITextField). Po prostu wróć wcześniej, jeśli textField != field3.
Ryan
25

Bardziej spójnym i niezawodnym sposobem jest użycie NextResponderTextField. Można go skonfigurować całkowicie z poziomu konstruktora interfejsów, bez potrzeby ustawiania delegata ani używania view.tag.

Wszystko, co musisz zrobić, to

  1. Ustaw typ klasy twoich UITextFieldbyćNextResponderTextField wprowadź opis zdjęcia tutaj
  2. Następnie ustaw wylot, nextResponderFieldaby wskazywał na następny obiekt odpowiadający, może to być UITextFielddowolna lub dowolna UIResponderpodklasa. Może to być także przycisk UIB, a biblioteka jest wystarczająco inteligentna, aby wywołać TouchUpInsidezdarzenie przycisku tylko wtedy, gdy jest włączone. wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Oto biblioteka w akcji:

wprowadź opis zdjęcia tutaj

mohamede1945
źródło
Tak. Działa naprawdę dobrze z Swift 3.1, bardzo eleganckim rozwiązaniem, tak jak IB powinien działać od razu po wyjęciu z pudełka.
Richard
Przepraszam, nie dostałem pytania.
mohamede1945
1
NextResponderTextField pracował dla mnie. Był szybki i łatwy w użyciu, tylko jeden szybki plik i zrobił dokładnie to, czego potrzebowałem bez żadnych problemów.
Pat McG
To działa świetnie! Uwaga: musiałem ręcznie zmienić klawisz Return, aby wyświetlał „Dalej” i „Gotowe” jak w przykładzie.
Mike Miller,
14

Lubię rozwiązania OO, które zostały już zasugerowane przez Anth0 i Answerbot. Pracowałem jednak nad szybkim i małym POC, więc nie chciałem zaśmiecać rzeczy podklasami i kategoriami.

Innym prostym rozwiązaniem jest utworzenie zestawu pól NSA i wyszukiwanie następnego pola po naciśnięciu przycisku next. Nie jest to rozwiązanie OO, ale szybkie, proste i łatwe do wdrożenia. Możesz również zobaczyć i zmodyfikować kolejność na pierwszy rzut oka.

Oto mój kod (oparty na innych odpowiedziach w tym wątku):

@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;
}
  • Zawsze zwracam NIE, ponieważ nie chcę wstawiania podziału linii. Pomyślałem, że zwrócę na to uwagę, ponieważ kiedy zwróciłem TAK, automatycznie opuszcza kolejne pola lub wstawia podział linii w moim TextView. Zrozumienie tego zajęło mi trochę czasu.
  • activeField śledzi aktywne pole na wypadek, gdyby konieczne było przewijanie, aby usunąć pole z klawiatury. Jeśli masz podobny kod, upewnij się, że przypisałeś activeField przed zmianą pierwszego respondera. Zmiana pierwszego odpowiadającego jest natychmiastowa i natychmiast uruchomi zdarzenie KeyboardWasShown.
Duży namiot
źródło
Miły! Prosty, a kod jest w jednym modelu. AnthO's jest również bardzo miły.
Seamus
Pamiętaj, że z tym kodem ryzykujesz zachowanie cykli. Twój obiekt fieldArray konwertuje słabe odniesienia używane we wszystkich twoich IBOutletach na silne odwołania w samej tablicy.
Michael Long
11

Oto implementacja tabulacji przy użyciu kategorii w UIControl. To rozwiązanie ma wszystkie zalety metod Michaela i Anth0, ale działa dla wszystkich UIControls, nie tylko UITextFields. Działa również płynnie z konstruktorem interfejsów i scenorysami.

Źródło i przykładowa aplikacja: repozytorium GitHub dla UIControlsWithTabbing

Stosowanie:

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

Przypisywanie nextControl w Konstruktorze interfejsów

Nagłówek:

//
// UIControl+NextControl.h
// UIControlsWithTabbing
//

#import <UIKit/UIKit.h>

@interface UIControl (NextControl)

@property (nonatomic, weak) IBOutlet UIControl *nextControl;

- (BOOL)transferFirstResponderToNextControl;

@end

Realizacja:

#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
źródło
1
Czasami następny widżet nie jest kontrolką, tj. UITextView, i możesz tam również użyć karty. Rozważ zmianę typu z UITextField na UIResponder.
Pedro Borges
1
Działa świetnie i ma fantastyczną dokumentację. Chciałbym przewinąć w dół do tego pytania kilka godzin temu. :)
Javier Cadiz
10

Wypróbowałem wiele kodów i na koniec zadziałało to w Swift 3.0 Latest [marzec 2017]

ViewControllerKlasa powinna być odziedziczył UITextFieldDelegatedo wykonywania tej pracy kodu.

class ViewController: UIViewController,UITextFieldDelegate  

Dodaj pole tekstowe z odpowiednim numerem znacznika, a ten numer znacznika służy do przeniesienia kontroli do odpowiedniego pola tekstowego na podstawie przypisanego mu przyrostowego numeru znacznika.

override func viewDidLoad() {
    userNameTextField.delegate = self
    userNameTextField.tag = 0
    userNameTextField.returnKeyType = UIReturnKeyType.next
    passwordTextField.delegate = self
    passwordTextField.tag = 1
    passwordTextField.returnKeyType = UIReturnKeyType.go
}

W powyższym kodzie, returnKeyType = UIReturnKeyType.nextgdzie sprawi, że klawisz powrotu klawiatury będzie wyświetlany, ponieważ Nextmasz również inne opcje, takie jak Join/Goitp., W zależności od aplikacji zmień wartości.

Jest textFieldShouldReturnto metoda kontrolowana przez UITextFieldDelegate i tutaj mamy następny wybór pola na podstawie przyrostu wartości Tag

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
 }
BHUVANESH MOHANKUMAR
źródło
Działa to, chyba że pole wejściowe to UITextView. Masz pomysł, jak to zrobić w połączeniu?
T9b
@ T9b Czego dokładnie potrzebujesz? Jakiej kontroli typu używasz w swojej aplikacji?
BHUVANESH MOHANKUMAR
Istnieją dwa typy wprowadzania tekstu, pierwszy to UITextField(wprowadź pojedynczy wiersz tekstu), drugi to UITextView(wprowadź wiele wierszy). UITextViewoczywiście nie odpowiada na ten kod, ale można go użyć w formularzach.
T9b
Tak, ta część kodu tylko dla UITextField i czy próbowałeś zmodyfikować zdarzenie dla UITextView?
BHUVANESH MOHANKUMAR
9

Po wyjściu z jednego pola tekstowego wywołujesz [otherTextField staje sięFirstResponder], a następne pole staje się aktywne.

Może to być trudny problem, ponieważ często będziesz chciał przewijać ekran lub w inny sposób zmieniać pozycję pola tekstowego, aby łatwo było go zobaczyć podczas edycji. Po prostu upewnij się, że wykonałeś wiele testów, wchodząc i wychodząc z pól tekstowych na różne sposoby, a także wychodząc wcześniej (zawsze daj użytkownikowi opcję zwolnienia klawiatury zamiast przechodzenia do następnego pola, zwykle z „Gotowe” w pasek nawigacyjny)

Kendall Helmstetter Gelner
źródło
9
 -(BOOL)textFieldShouldReturn:(UITextField *)textField
{
   [[self.view viewWithTag:textField.tag+1] becomeFirstResponder];
   return YES;
}
iKushal
źródło
Nie należy używać do tego celu tagów.
Christian Schnorr,
7

Bardzo łatwą metodą zwolnienia klawiatury po naciśnięciu przycisku „Gotowe” jest:

Utwórz nowy IBAction w nagłówku

- (IBAction)textFieldDoneEditing:(id)sender;

W pliku implementacji (plik .m) dodaj następującą metodę:

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

Następnie, gdy dojdziesz do połączenia IBAction z polem tekstowym - link do wydarzenia „Did End On Exit”.

jcrowson
źródło
1
Nie ma za co, nie porusza się po polach tekstowych, ale zamyka klawiaturę i pozwala wybrać inne pole.
jcrowson
7

Najpierw ustaw klawisz powrotu na klawiaturze w Xib, w przeciwnym razie możesz napisać kod w 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;
}
mahesh chowdary
źródło
7

Jestem zaskoczony, jak wiele odpowiedzi tutaj nie rozumie jednej prostej koncepcji: poruszanie się po elementach sterujących w aplikacji nie jest czymś, co same widoki powinny robić. Zadaniem kontrolera jest podjęcie decyzji, która kontrola ma być następną pierwszą odpowiedzią.

Większość odpowiedzi dotyczyła tylko nawigacji do przodu, ale użytkownicy mogą chcieć cofnąć się.

Oto co wymyśliłem. Formularzem powinien zarządzać kontroler widoku, a kontrolery widoku są częścią łańcucha respondentów. Zatem możesz całkowicie zaimplementować następujące metody:

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

Może obowiązywać dodatkowa logika przewijania odpowiedzi poza ekranem widocznych wcześniej.

Kolejną zaletą tego podejścia jest to, że nie trzeba podklasować wszystkich rodzajów elementów sterujących, które możesz chcieć wyświetlić (takich jak UITextFields), ale możesz zamiast tego zarządzać logiką na poziomie kontrolera, gdzie, szczerze mówiąc, jest to właściwe miejsce do tego .

Christian Schnorr
źródło
Uważam, że twoja odpowiedź jest najbardziej istotna, ale zauważyłem coś śmiesznego, co być może uda ci się wyjaśnić. Podczas korzystania z UITextFields, które nie są zarządzane przez UIViewController, naciśnięcie klawisza tab spowoduje domyślnie, że następny UITextField będzie pierwszym odpowiadającym. Jednak gdy każdy jest zarządzany przez VC, nie jest to prawdą. Moje VC nic nie robią, ale nie ma wbudowanej funkcji tabulacji. Zauważyłem, że keyCommands VC kończy się dodawaniem do łańcucha odpowiadającego widoku. To prowadzi mnie do przekonania, że ​​jeśli używasz VC, obowiązkowe jest wdrożenie keyCommands.
Joey Carson
4

Dodałem do odpowiedzi PeyloW na wypadek, gdybyś chciał zaimplementować funkcję przycisku poprzedni / następny:

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

    }
}

Jeśli podklasujesz UIView w następujący sposób:

@implementation UIView (FindAndReturnFirstResponder)
- (UITextView *)findFirstResponderAndReturn
{
    for (UITextView *subView in self.subviews) {
        if (subView.isFirstResponder){
            return subView;
        }
    }
    return nil;
}
@end
James Mertz
źródło
4

Cześć wszystkim, proszę zobaczyć ten

- (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
źródło
Zamiast respondenta UIView * dokładniej byłoby użyć respondera UIRespoder *
Pedro Borges
4

Próbowałem rozwiązać ten problem, stosując bardziej wyrafinowane podejście oparte na przypisywaniu każdej komórce (lub UITextField) UITableViewunikalnej wartości znacznika, którą można później odzyskać: aktywuj-następny-uitextfield-in-uitableview-ios

Mam nadzieję, że to pomoże!

Fabiano Francesconi
źródło
4

Właśnie stworzyłem nową kapsułę podczas pracy z tymi rzeczami GNTextFieldsCollectionManager . Automatycznie obsługuje następny / ostatni problem z polem tekstowym i jest bardzo łatwy w użyciu:

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

Pobiera wszystkie pola tekstowe posortowane według hierarchii widoku (lub według znaczników), lub możesz określić własną tablicę pól tekstowych.

JakubKnejzlik
źródło
4

Rozwiązanie w Swift 3.1, po połączeniu pól tekstowych IBOutlets ustaw delegowanie pól tekstowych w viewDidLoad, a następnie nawiguj swoją akcją w 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
        }

    }
Krishna Thakur
źródło
4

Jeśli ktoś tak chce. Myślę, że jest to najbliższe wymaganym pytaniom

wprowadź opis zdjęcia tutaj

Oto jak zaimplementowałem ten

Dodaj widok akcesoriów dla każdego pola tekstowego, dla którego chcesz skonfigurować, używając

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
}

Do obsługi kranów służą następujące funkcje

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
}

Musisz podać tagi textFields w kolejności, w której ma odpowiadać przycisk Dalej / Wstecz.

Mohammad Sadiq
źródło
3

Wolę:

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

W pliku NIB podpinam textFields w żądanej kolejności do tej tablicy inputFields. Następnie wykonuję prosty test dla indeksu UITextField, który zgłasza, że ​​użytkownik pukał:

// 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;
}
Antykro
źródło
3
if (komórka == zero)
{
    cell = [[Przydział UITableViewCell] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellIdentifier];
    txt_Input = [[Alokacja UITextField] initWithFrame: CGRectMake (0, 10, 150, 30)];
    txt_Input.tag = indexPath.row + 1;
    [self.array_Textfields addObject: txt_Input]; // Zainicjuj zmienną tablicę w ViewDidLoad
}

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

    int tag = (int) textField.tag;
    UITextField * txt = [self.array_Textfields objectAtIndex: tag];
    [txt ZostańFirstResponder];
    zwróć TAK;
}
Rebecca Curties
źródło
3

Miałem około 10+ UITextField na mojej tablicy opowieści, a sposób, w jaki włączyłem następną funkcjonalność, polegał na utworzeniu tablicy UITextField i uczynieniu następnego UITextField pierwszą odpowiedzią. Oto plik implementacyjny:

#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
Nielegalny argument
źródło
3

Działa to dla mnie w Xamarin.iOS / Monotouch. Zmień przycisk klawiatury na Dalej, przekaż formant do następnego pola UITextField i ukryj klawiaturę po ostatnim polu 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.
    };
  }
}

Wewnątrz ViewDidLoad będziesz mieć:

Jeśli Twoje TextFields nie mają tagu, ustaw go teraz:

txtField1.Tag = 0;
txtField2.Tag = 1;
txtField3.Tag = 2;
//...

i tylko połączenie

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.
Daniele D.
źródło
3

Jest to proste, szybkie rozwiązanie, bez użycia tagów, bez sztuczek w serii ujęć ...

Po prostu użyj tego rozszerzenia:

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
    }

}

I nazwij to tak (na przykład) z delegatem pola tekstowego:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    textField.nextTextFieldField()?.becomeFirstResponder()
    return true
}
Kevin ABRIOUX
źródło
Jednym z komentarzy jest to, że twoja metoda zakłada, że ​​wszystkie pola są ułożone pionowo w jednym superwizji. Nie pozwala na zgrupowanie pól i nie działa, gdy pola tekstowe mogą być obok siebie. To ostatnie jest szczególnie ważne w dzisiejszych czasach, gdy używa się klas wielkości do zmiany kolejności formularzy dla widoków poziomych i gdy formularze są wyświetlane na tabletach.
Michael Long
3

Bezpieczniejszy i bardziej bezpośredni sposób, przy założeniu:

  • delegaci pola tekstowego są przypisani do kontrolera widoku
  • wszystkie pola tekstowe są widokami podrzędnymi tego samego widoku
  • pola tekstowe mają znaczniki w kolejności, w której chcesz postępować (np. textField2.tag = 2, textField3.tag = 3 itd.)
  • przejście do następnego pola tekstowego nastąpi po dotknięciu przycisku powrotu na klawiaturze (możesz zmienić to na następne , gotowe itp.)
  • chcesz, aby klawiatura odrzuciła się po ostatnim polu tekstowym

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

    }
}
Jason Z
źródło
2

w textFieldShouldReturn powinieneś sprawdzić, czy pole tekstowe, na którym aktualnie się znajdujesz, nie jest ostatnie, gdy klikną następne i czy nie nie odrzuci klawiatury.

Daniel
źródło
Hej, ok, to działa, ale jak mogę przejść do następnego pola tekstowego za pomocą przycisku „Dalej”?
phx
2

To jest stary post, ale ma wysoką pozycję w rankingu, więc wprowadzę moje rozwiązanie.

Miałem podobny problem i ostatecznie stworzyłem podklasę UIToolbarzarządzania następną / poprzednią / wykonaną funkcją w dynamicznym tableView z sekcjami: https://github.com/jday001/DataEntryToolbar

Ustawiasz pasek narzędzi jako inputAccessoryView swoich pól tekstowych i dodajesz je do słownika. Umożliwia to przewijanie ich do przodu i do tyłu, nawet w przypadku treści dynamicznych. Istnieją metody delegowania, jeśli chcesz uruchomić własną funkcjonalność, gdy nastąpi nawigacja textField, ale nie musisz zajmować się zarządzaniem żadnymi tagami ani statusem pierwszego odpowiadającego.

W linku GitHub znajdują się fragmenty kodu i przykładowa aplikacja, która pomaga w szczegółach implementacji. Będziesz potrzebował własnego modelu danych, aby śledzić wartości w polach.

jday
źródło
2

Bez znaczników usings i bez dodawania właściwości dla nextField / nextTextField możesz spróbować emulować TAB, gdzie „testInput” to twoje aktywne pole:

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];
                                                    }];
Cœur
źródło
2

Korzystam z odpowiedzi Michaela G. Emmonsa od około roku, działa świetnie. Zauważyłem niedawno, że wywołanie resignFirstResponder, a następnie stanie sięFirstResponder natychmiast, może spowodować „usterkę” klawiatury, zniknięcie, a następnie natychmiastowe pojawienie się. Lekko zmieniłem jego wersję, aby pominąć resignFirstResponder, jeśli dostępne jest pole nextField.

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

    if ([textField isKindOfClass: [NRTextField class]])
    {
        NRTextField * nText = (NRTextField *) textField;
        if ([nText nextField]! = zero) {
            dispatch_async (dispatch_get_main_queue (),
                           ^ {[[nText nextField] staje sięFirstResponder]; });

        }
        jeszcze{
            [textField resignFirstResponder];
        }
    }
    jeszcze{
        [textField resignFirstResponder];
    }

    zwróć prawdę;

}
arinmorf
źródło