Zdarzenie zmiany tekstu UITextField

563

Jak mogę wykryć jakiekolwiek zmiany tekstu w polu tekstowym? Metoda delegowania shouldChangeCharactersInRangena coś działa, ale nie spełniła dokładnie mojej potrzeby. Ponieważ dopóki nie zwróci TAK, teksty textField nie są dostępne dla innych metod obserwatora.

np. w moim kodzie calculateAndUpdateTextFieldsnie dostałem zaktualizowanego tekstu, użytkownik wpisał.

Jest ich sposobem na uzyskanie czegoś takiego jak textChangedmoduł obsługi zdarzeń Java.

- (BOOL)textField:(UITextField *)textField 
            shouldChangeCharactersInRange:(NSRange)range 
            replacementString:(NSString *)string 
{
    if (textField.tag == kTextFieldTagSubtotal 
        || textField.tag == kTextFieldTagSubtotalDecimal
        || textField.tag == kTextFieldTagShipping
        || textField.tag == kTextFieldTagShippingDecimal) 
    {
        [self calculateAndUpdateTextFields];

    }

    return YES;
}
karim
źródło
3
To dobry przykład odpowiedzi SO z 1000 głosów, która jest całkowicie niepoprawna . iOS jest trudny, elegancki i pełen gotchy.
Fattie

Odpowiedzi:

1056

Od właściwego sposobu zmiany tekstu uitextfield oddzwoń :

Łapię znaki wysyłane do kontrolki UITextField coś takiego:

// Add a "textFieldDidChange" notification method to the text field control.

W celu C:

[textField addTarget:self 
              action:@selector(textFieldDidChange:) 
    forControlEvents:UIControlEventEditingChanged];

W Swift:

textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)

Następnie w textFieldDidChangemetodzie możesz sprawdzić zawartość pola tekstowego i w razie potrzeby załadować ponownie widok tabeli.

Można użyć tego i umieścić calculateAndUpdateTextFields jak twoje selector.

Daniel G. Wilson
źródło
18
Jest to lepsze rozwiązanie - ponieważ można to również ustawić w stalówkach lub scenorysach i nie trzeba pisać nadmiernego kodu
UITextFieldTextDidChangeNotification
3
Jedynym problemem jest. Nie działa dla mnie razem z - (BOOL) textField: (UITextField *) textField powinienChangeCharactersInRange: (NSRange) zamiennik zakresuString: (NSString *) string
iWheelBuy
4
@ iWheelBuy Tylko wtedy, gdy zwraca NO, co jest logiczne, ponieważ po powrocie NOz tej metody w zasadzie mówisz, że tekst w polu nie powinien się zmieniać.
gitaarik
2
Jest to świetne do edycji spowodowanych zmianami. To nie złapać programowe zmiany zachodzące poprzez: textField.text = "Some new value";. Czy istnieje sprytny sposób na złapanie tego?
Benjohn
10
Można by pomyśleć z Apple, UITextFieldDelegateże coś takiego func textField: UITextField, didChangeText text: Stringzostałoby uwzględnione, ale ... (obrzuca facetów z Apple nieprzyzwoitym wyglądem)
Brandon
394

Odpowiedź XenElement jest natychmiastowa.

Powyższe można również wykonać w narzędziu do tworzenia interfejsów, klikając prawym przyciskiem myszy pole UITextField i przeciągając zdarzenie wysyłania „Edycja zmieniona” do jednostki podklasy.

Zdarzenie zmiany UITextField

William T.
źródło
2
Może to być łatwe, ale po 10-15 minutach poszukiwań nie odkryłem tej możliwości, dopóki przypadkiem nie natknąłem się na twoją odpowiedź. Otrzymaj ode mnie kolejne +1; miło jest mieć głosowanie zarówno w przypadku rozwiązań opartych na kodzie, jak i opartych na IB, na takie pytania.
Mark Amery
Właśnie sprawdziłem w 6.3 i już tam jest. To jest UITextView, kliknij go prawym przyciskiem myszy i zobacz „Edycja zmieniona”.
William T.
4
dlaczego, u licha, nazywa się Edycja Zmieniona? szalony! dzięki za rozwiązanie :-)
malhal
3
Ponieważ zdarzenie jest wywoływane podczas edycji zmian, natomiast zmiana wartości ma miejsce, gdy pole tekstowe zakończy edycję, tzn. Zrezygnuje z pierwszej odpowiedzi. UITextFieldTextDidChangeNotification to powiadomienie UITextField, natomiast UIControlEventValueChanged jest implementowany przez UIControl, który podklasy UIButton.
Camsoft,
2
Zauważyłem, że to nie działa, gdy karty użytkownika
znikają z pola
136

aby ustawić detektor zdarzeń:

[self.textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];

faktycznie słuchać:

- (void)textFieldDidChange:(UITextField *)textField {
    NSLog(@"text changed: %@", textField.text);
}
asdf
źródło
fajne. Podoba mi się to podejście, ponieważ chciałbym zaimplementować nasłuchiwanie zmiany textField w klasie bazowej ViewController, która jest już TextFieldDelegate. W ten sposób mogę dodać Cel: baseControllerMethod dla (textField w widokach podrzędnych) jak urok. DZIĘKI!
HBublitz
87

Szybki:

yourTextfield.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)

Następnie zaimplementuj funkcję zwrotną:

@objc func textFieldDidChange(textField: UITextField){

print("Text changed")

}
Juan Boero
źródło
2
Pracowałem dla mnie, próbowałem już tego, ale nie zdawałem sobie sprawy, że funkcja wymaga argumentu textField. Jeśli to nie działa, upewnij się, że twój selektor ma argument za przesłaniem textField (nawet jeśli go nie używasz).
werm098
Dodaj _ przed textField w textFieldDidChange w ten sposób: func textFieldDidChange (textField: UITextField) {...}
Makalele
30

Jak stwierdzono tutaj: Zdarzenie zmiany tekstu UITextField , wydaje się, że od iOS 6 (sprawdzone iOS 6.0 i 6.1) nie jest możliwe pełne wykrycie zmian w UITextFieldobiektach po prostu obserwując UITextFieldTextDidChangeNotification.

Wygląda na to, że śledzone są teraz tylko te zmiany wprowadzone bezpośrednio przez wbudowaną klawiaturę iOS. Oznacza to, że jeśli zmienisz UITextFieldobiekt tylko przez wywołanie czegoś takiego: myUITextField.text = @"any_text"nie otrzymasz powiadomienia o żadnych zmianach.

Nie wiem, czy to błąd, czy zamierzony. Wygląda na błąd, ponieważ nie znalazłem żadnego uzasadnionego wyjaśnienia w dokumentacji. Jest to również stwierdzone tutaj: Zdarzenie zmiany tekstu UITextField .

Moje „rozwiązanie” tego problemu polega na tym, że faktycznie wysyłam powiadomienie o każdej wprowadzonej przeze mnie zmianie UITextField(jeśli ta zmiana zostanie dokonana bez użycia wbudowanej klawiatury iOS). Coś takiego:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

NSNotification *myTextFieldUpdateNotification  = 
  [NSNotification notificationWithName:UITextFieldTextDidChangeNotification
                  object:myUITextField];

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

W ten sposób masz 100% pewności, że otrzymasz takie samo powiadomienie po zmianie .textwłaściwości swojegoUITextField obiektu, albo kiedy zaktualizujesz go „ręcznie” w kodzie, albo za pomocą wbudowanej klawiatury iOS.

Należy wziąć pod uwagę, że ponieważ nie jest to udokumentowane zachowanie, takie podejście może prowadzić do otrzymania 2 powiadomień o tej samej zmianie w UITextFieldobiekcie. W zależności od potrzeb (co faktycznie robisz po UITextField.textwprowadzeniu zmian) może to być dla Ciebie niedogodne.

Nieco innym podejściem byłoby opublikowanie niestandardowego powiadomienia (to znaczy o niestandardowej nazwie innej niż UITextFieldTextDidChangeNotification), jeśli faktycznie chcesz wiedzieć, czy powiadomienie było twoje, czy „wykonane na iOS”.

EDYTOWAĆ:

Właśnie znalazłem inne podejście, które moim zdaniem mogłoby być lepsze:

Dotyczy to funkcji Key-Value Observing (KVO) Objective-C ( http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid / 10000177-BCICJDHA ).

Zasadniczo rejestrujesz się jako obserwator nieruchomości, a jeśli ta właściwość się zmieni, zostaniesz o tym powiadomiony. „Zasada” jest dość podobna do tego NSNotificationCenter, jak działa, jest to główna zaleta, że ​​to podejście działa automatycznie również od iOS 6 (bez żadnych specjalnych poprawek, takich jak konieczność ręcznego publikowania powiadomień).

W naszym UITextFieldscenariuszu działa to dobrze, jeśli dodasz ten kod, na przykład do tego, UIViewControllerktóry zawiera pole tekstowe:

static void *myContext = &myContext;

- (void)viewDidLoad {
  [super viewDidLoad];

  //Observing changes to myUITextField.text:
  [myUITextField addObserver:self forKeyPath:@"text"
    options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld 
    context:myContext];

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
change:(NSDictionary *)change context:(void *)context {

  if(context == myContext) {
    //Here you get notified every time myUITextField's "text" property is updated
    NSLog(@"New value: %@ - Old value: %@",
      [change objectForKey:NSKeyValueChangeNewKey],
      [change objectForKey:NSKeyValueChangeOldKey]);
  }
  else 
    [super observeValueForKeyPath:keyPath ofObject:object 
      change:change context:context];

}

Podziękowania dla tej odpowiedzi dotyczącej zarządzania „kontekstem”: https://stackoverflow.com/a/12097161/2078512

Uwaga: Wydaje się, gdy jesteś w procesie edycji UITextFieldz wbudowaną klawiaturą iOS, „Tekst” własność pola tekstowego nie jest aktualizowany przy każdej nowej litery wpisane / usunięte. Zamiast tego obiekt pola tekstowego jest aktualizowany „jako całość” po rezygnacji ze statusu pierwszego pola odpowiadającego w polu tekstowym.

ercolemtar
źródło
5
Po co sobie z tym radzić, wystarczy użyć metody target-action z odpowiedzi XenElement. Jeśli potrzebujesz dziesiątek wierszy kodu, zrób coś, co „powinno” być proste, prawdopodobnie robisz to źle.
jrturton
6
To wyglądałoby na zamierzone zachowanie. Żadne inne metody delegowania (np. Przewijanie, wybór) nie są wywoływane w przypadku zdarzeń pochodzących z kodu.
jrturton
1
Pamiętaj, że UIKit nie gwarantuje zgodności z KVO , więc rozwiązanie KVO niekoniecznie będzie działać.
Pang
@ jrturton, jeśli chodzi o twoje pytanie „dlaczego”, to zupełnie powszechne, że w niektórych innych klasach (na przykład dekoracji, animacji itp.) musisz odpowiedzieć na to, co dzieje się w polu tekstowym.
Fattie
27

Możemy łatwo to skonfigurować Storyboard, CTRL przeciągnij @IBActioni zmień zdarzenie w następujący sposób:

wprowadź opis zdjęcia tutaj

bikram sapkota
źródło
14

Tutaj w szybkiej wersji za to samo.

textField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)

func textFieldDidChange(textField: UITextField) {

}

Dzięki

Hindus
źródło
12

Rozwiązałem problem zmieniający zachowanie shouldChangeChractersInRange. Jeśli zwrócisz NIE, zmiany nie zostaną zastosowane wewnętrznie przez iOS, zamiast tego możesz zmienić je ręcznie i wykonać dowolne działania po zmianach.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    //Replace the string manually in the textbox
    textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
    //perform any logic here now that you are sure the textbox text has changed
    [self didChangeTextInTextField:textField];
    return NO; //this make iOS not to perform any action
}
Pauls
źródło
3
Problem z tym rozwiązaniem jest następujący: Pozycja kursora zostaje ustawiona na KONIEC pola (jak tylko zrobisz textField.text = ...). Jeśli więc użytkownik chce edytować znaki na środku pola, to po każdym naciśnięciu klawisza jego kursor będzie przechodził na koniec pola. Wypróbuj i przekonaj się.
FranticRock
Dobra uwaga @Alex, ale łatwa do obejścia. Wystarczy obliczyć wynikową wartość w lokalnym NSString i przekazać ją do metody didChangeTextInTextField: toText:, a następnie zwrócić TAK, aby iOS mógł wykonać aktualizację.
jk7
11

Testowana wersja Swift:

//Somewhere in your UIViewController, like viewDidLoad(){ ... }
self.textField.addTarget(
        self, 
        action: #selector(SearchViewController.textFieldDidChange(_:)),
        forControlEvents: UIControlEvents.EditingChanged
)

Parametry wyjaśnione:

self.textField //-> A UITextField defined somewhere in your UIViewController
self //-> UIViewController
.textFieldDidChange(_:) //-> Can be named anyway you like, as long as it is defined in your UIViewController

Następnie dodaj metodę utworzoną powyżej w UIViewController:

//Gets called everytime the text changes in the textfield.
func textFieldDidChange(textField: UITextField){

    print("Text changed: " + textField.text!)

}
duch kemicofa
źródło
7

Szybki 4

func addNotificationObservers() {

    NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidChangeAction(_:)), name: .UITextFieldTextDidChange, object: nil)

}

@objc func textFieldDidChangeAction(_ notification: NSNotification) {

    let textField = notification.object as! UITextField
    print(textField.text!)

}
acidgate
źródło
5

W przypadku Swift 3.0:

let textField = UITextField()

textField.addTarget(
    nil,
    action: #selector(MyClass.textChanged(_:)),
    for: UIControlEvents.editingChanged
)

za pomocą klasy takiej jak:

class MyClass {
    func textChanged(sender: Any!) {

    }
}
pedrouan
źródło
4

Wersja Swift 3

yourTextField.addTarget(self, action: #selector(YourControllerName.textChanges(_:)), for: UIControlEvents.editingChanged)

I zobacz zmiany tutaj

func textChanges(_ textField: UITextField) {
    let text = textField.text! // your desired text here
    // Now do whatever you want.
}

Mam nadzieję, że to pomoże.

Riajur Rahman
źródło
2

Powinieneś użyć powiadomienia, aby rozwiązać ten problem, ponieważ druga metoda nasłuchuje pola wprowadzania danych, a nie rzeczywistych danych wejściowych, szczególnie gdy używasz chińskiej metody wprowadzania danych. In viewDidload

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFiledEditChanged:)
                                                 name:@"UITextFieldTextDidChangeNotification"
                                               object:youTarget];

następnie

- (void)textFiledEditChanged:(NSNotification *)obj {
UITextField *textField = (UITextField *)obj.object;
NSString *toBestring = textField.text;
NSArray *currentar = [UITextInputMode activeInputModes];
UITextInputMode *currentMode = [currentar firstObject];
if ([currentMode.primaryLanguage isEqualToString:@"zh-Hans"]) {
    UITextRange *selectedRange = [textField markedTextRange];
    UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
    if (!position) {
        if (toBestring.length > kMaxLength)
            textField.text =  toBestring;
} 

}

w końcu uciekniesz, zrobisz.

HsuChihYung
źródło
2

Swift 3.1:

Selektor: nazwa klasy. Nazwa metody

  cell.lblItem.addTarget(self, action: #selector(NewListScreen.fieldChanged(textfieldChange:)), for: .editingChanged)

  func fieldChanged(textfieldChange: UITextField){

    }
Beyaz
źródło
2

Wersja Swift 3:

class SomeClass: UIViewController, UITextFieldDelegate { 

   @IBOutlet weak var aTextField: UITextField!


    override func viewDidLoad() {
        super.viewDidLoad()

        aTextField.delegate = self
        aTextField.addTarget(self, action: #selector(SignUpVC.textFieldDidChange), for: UIControlEvents.editingChanged)        
    }

   func textFieldDidChange(_ textField: UITextField) {

       //TEXT FIELD CHANGED..SECRET STUFF

   }

}

Nie zapomnij ustawić delegata.

Joseph Francis
źródło
Mam 6 pól tekstowych w moim kontrolerze widoku i chcę tylko sprawdzić, czy ostatnie pole tekstowe zostało zmienione wydrukuj („ostatnie pole tekstowe zostało zmienione!”), Czy możesz pomóc?
Saeed Rahmatolahi
Najpierw utwórz Iboutlet dla pola tekstowego. Następnie ustaw delegata i addTarget tak, jak pokazałem w mojej odpowiedzi. W funkcji delegowania textFieldDidChange dodaj: „if textField == yourLastTextField {print („ ostatnie pole tekstowe zostało zmienione! ”)} W tej chwili nie mam dostępu do mojego komputera. Uczynię ten komentarz bardziej czytelnym, gdy dostań się do mojego komputera
Joseph Francis,
2

Z zamknięciem:

   class TextFieldWithClosure: UITextField {
    var targetAction: (() -> Void)? {
        didSet {
            self.addTarget(self, action: #selector(self.targetSelector), for: .editingChanged)
        }
    }

    func targetSelector() {
        self.targetAction?()
    }
    }

i za pomocą

textField.targetAction? = {
 // will fire on text changed
 }
ebohda
źródło
2
  1. Rozwiązania KVO nie działają

KVO NIE działa w iOS dla kontroli: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

  1. W klasie obserwacyjnej robisz to:

Biorąc pod uwagę, że znasz widok tekstu, który chcesz obejrzeć:

var watchedTextView: UITextView!

Zrób to:

NotificationCenter.default.addObserver(
    self,
    selector: #selector(changed),
    name: UITextView.textDidChangeNotification,
    object: watchedTextView)

Uważaj jednak na to:

  • jest prawdopodobne, że chcesz zadzwonić tylko raz , więc nie dzwoń na przykładlayoutSubviews

  • trudno jest wiedzieć, kiedy najlepiej zadzwonić w trakcie procesu wychowawczego. Będzie to zależeć od twojej sytuacji. Niestety nie ma standardowego, zamkniętego rozwiązania

  • na przykład zwykle z pewnością nie możesz zadzwonić na initczas, ponieważ oczywiście watchedTextViewmoże jeszcze nie istnieć

.

  1. kolejnym problemem jest to, że ...

Żadne z powiadomień nie jest wywoływane, gdy tekst jest zmieniany programowo .

To ogromna, odwieczna i głupia uciążliwość w inżynierii iOS.

Sterowanie po prostu nie - koniec historii - wywołuje powiadomienia, gdy właściwość .text jest programowo zmieniana.

Jest to niezwykle denerwujące, ponieważ - oczywiście - każda aplikacja, która kiedykolwiek wyprodukowała, ustawia tekst programowo, na przykład czyści pole po postach użytkownika itp.

Musisz podklasować widok tekstu (lub podobny element sterujący) w następujący sposób:

class NonIdioticTextView: UIITextView {

    override var text: String! {
        // boilerplate code needed to make watchers work properly:
        get {
            return super.text
        }
        set {
            super.text = newValue
            NotificationCenter.default.post(
                name: UITextView.textDidChangeNotification,
                object: self)
        }

    }

}

(Wskazówka - nie zapomnij, że super połączenie musi przyjść wcześniej !

Nie ma dostępnego rozwiązania, chyba że naprawisz kontrolę przez podklasowanie, jak pokazano powyżej. To jest jedyne rozwiązanie.

Pamiętaj, że powiadomienie

UITextView.textDidChangeNotification

prowadzi do

func textViewDidChangeSelection(_ textView: UITextView) 

być nazwanym.

( Nie textViewDidChange .)

Fattie
źródło
1
[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(didChangeTextViewText:) 
name:UITextFieldTextDidChangeNotification object:nil];



- (void) didChangeTextViewText {
 //do something
}
PeiweiChen
źródło
0

Jedną rzeczą jest to, że możesz mieć wiele pól UITextFields. Daj im tag, a następnie możesz włączyć tagi. Oto jak ustawić obserwatora w dowolnej klasie.

private func setupTextFieldNotification() {
    NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in
        if let textField = notification.object as? UITextField, textField.tag == 100, let text = textField.text {
            print(#line, text)
        }
    }
}

deinit {
    NotificationCenter.default.removeObserver(self)
}
smileBot
źródło
-1

Wersja Swift 4

Korzystanie z obserwacji klucz-wartość Powiadamiaj obiekty o zmianach właściwości innych obiektów.

var textFieldObserver: NSKeyValueObservation?

textFieldObserver = yourTextField.observe(\.text, options: [.new, .old]) { [weak self] (object, changeValue) in
  guard let strongSelf = self else { return }
  print(changeValue)
}
Valeriy
źródło
Dobry pomysł. czy możesz to zawrzeć w kontekście i dać nam znać?
Revanth Kausikan
1
Jest to niestety całkowicie błędne. KVO NIE działa w iOS dla formantów: stackoverflow.com/a/6352525/1402846 developer.apple.com/library/archive/documentation/General/...
Fattie