Evento di modifica del testo UITextField


563

Come posso rilevare eventuali modifiche al testo in un campo di testo? Il metodo delegato shouldChangeCharactersInRangefunziona per qualcosa, ma non ha soddisfatto esattamente le mie necessità. Poiché fino a quando non restituisce SÌ, i testi textField non sono disponibili per altri metodi di osservazione.

ad esempio nel mio codice calculateAndUpdateTextFieldsnon ho ricevuto il testo aggiornato, l'utente ha digitato.

È il loro modo per ottenere qualcosa come il textChangedgestore di eventi 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;
}

3
Questo è un buon esempio di risposta SO con 1000 voti che è completamente errata . iOS è complicato, delicato e pieno di gotchya.
Fattie

Risposte:


1056

Dal modo corretto per fare uitextfield text change, richiamare :

Catturo i personaggi inviati a un controllo UITextField in questo modo:

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

In Objective-C:

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

In Swift:

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

Quindi nel textFieldDidChangemetodo è possibile esaminare il contenuto di textField e ricaricare la visualizzazione della tabella in base alle esigenze.

Potresti usarlo e mettere calcoliAndUpdateTextFields come tuo selector.


18
Questa è la soluzione migliore - perché si può anche impostare questo in pennini o Storyboard, e non c'è bisogno di scrivere troppo codice UITextFieldTextDidChangeNotification
PostCodeism

3
L'unico problema è. Non funziona per me insieme a - (BOOL) textField: (UITextField *) textField shouldChangeCharactersInRange: sostituzione intervallo (NSRange) Stringa: (NSString *) stringa
iWheelBuy

4
@iWheelBuy Solo quando ritorna NO, il che è logico, perché quando torni NOda questo metodo, in pratica stai dicendo che il testo nel campo non dovrebbe cambiare.
Gitaarik,

2
Questo è ottimo per la modifica dei cambiamenti causati. Non cattura le modifiche programmatiche che si verificano attraverso: textField.text = "Some new value";. C'è un modo intelligente per prenderlo?
Benjohn,

10
Avresti pensato con Apple UITextFieldDelegateche qualcosa del genere func textField: UITextField, didChangeText text: Stringsarebbe stato incluso, ma ... (dà ai ragazzi di Apple uno sguardo sporco)
Brandon A

394

La risposta di XenElement è perfetta.

Quanto sopra può essere fatto anche nel builder di interfacce facendo clic con il pulsante destro del mouse su UITextField e trascinando l'evento di invio "Modifica modificata" sull'unità della sottoclasse.

Evento di modifica UITextField


2
Può essere facile, ma dopo 10-15 minuti di ricerche, non avevo scoperto questa possibilità fino a quando non mi sono imbattuto nella tua risposta per caso. Ricevi un altro +1 da parte mia; è bello avere entrambe le soluzioni basate su codice e basate su IB altamente votate su domande come questa.
Mark Amery,

L'ho appena verificato in 6.3 ed è lì. Questo è un UITextView e fai clic destro su di esso e vedi "Modifica modificata".
William T.

4
perché mai si chiama Modifica modificata ?? pazzo! grazie per la soluzione però :-)
malhal

3
Perché l'evento viene chiamato durante la modifica delle modifiche, mentre Value Changed si verifica quando il campo di testo termina la modifica, ovvero si dimette dal primo risponditore. UITextFieldTextDidChangeNotification è una notifica UITextField, mentre UIControlEventValueChanged è implementato da UIControl che sottoclasse UIButton.
Camsoft,

2
Ho notato che questo non funziona come le schede utente fuori dal campo di testo e un testo di correzione automatica sostituisce il contenuto
noobular

136

per impostare il listener di eventi:

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

per ascoltare effettivamente:

- (void)textFieldDidChange:(UITextField *)textField {
    NSLog(@"text changed: %@", textField.text);
}

freddo. Mi piace questo approccio, perché mi piacerebbe implementare textField-change-listening in una classe base ViewController che è già TextFieldDelegate. In questo modo posso aggiungere Target: baseControllerMethod per (textField in sottoview) come un incantesimo. GRAZIE!
HBublitz,

87

Swift:

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

Quindi, implementare la funzione di callback:

@objc func textFieldDidChange(textField: UITextField){

print("Text changed")

}

2
Ha funzionato per me, l'avevo già provato ma non mi rendevo conto che la funzione avesse bisogno di un argomento textField. Se questo non funziona per te, assicurati di controllare che il tuo selettore abbia un argomento per il textField da passare (anche se non lo usi).
werm098,

Aggiungi _ prima di textField in textFieldDidCambia in questo modo: func textFieldDidChange (textField: UITextField) {...}
Makalele

30

Come indicato qui: evento di modifica del testo UITextField , sembra che a partire da iOS 6 (iOS 6.0 e 6.1 selezionati) non sia possibile rilevare completamente le modifiche negli UITextFieldoggetti solo osservando il UITextFieldTextDidChangeNotification.

Sembra che ora vengano monitorate solo le modifiche apportate direttamente dalla tastiera iOS integrata. Ciò significa che se modifichi il tuo UITextFieldoggetto semplicemente invocando qualcosa del genere: myUITextField.text = @"any_text"non verrai avvisato di eventuali cambiamenti.

Non so se questo è un bug o è previsto. Sembra un bug poiché non ho trovato alcuna spiegazione ragionevole nella documentazione. Questo è indicato anche qui: Evento di modifica del testo UITextField .

La mia "soluzione" a questo è in realtà pubblicare una notifica da solo per ogni modifica UITextFieldapportata alla mia (se tale modifica viene eseguita senza utilizzare la tastiera iOS integrata). Qualcosa come questo:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

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

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

In questo modo sei sicuro al 100% che riceverai la stessa notifica quando cambi la .textproprietà del tuo UITextFieldoggetto, sia quando lo aggiorni "manualmente" nel tuo codice sia attraverso la tastiera iOS integrata.

È importante considerare che, poiché questo non è un comportamento documentato, questo approccio può portare a 2 notifiche ricevute per la stessa modifica UITextFieldnell'oggetto. A seconda delle tue esigenze (cosa fai effettivamente quando le tue UITextField.textmodifiche) questo potrebbe essere un inconveniente per te.

Un approccio leggermente diverso sarebbe quello di pubblicare una notifica personalizzata (vale a dire, con un nome personalizzato diverso da UITextFieldTextDidChangeNotification) se in realtà è necessario sapere se la notifica è stata tua o "iOS-made".

MODIFICARE:

Ho appena trovato un approccio diverso che penso potrebbe essere migliore:

Implica la funzionalità KVO (Key-Value Observing) di Objective-C ( http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid / 10000177-BCICJDHA ).

Fondamentalmente, ti registri come osservatore di una proprietà e se questa proprietà cambia, ne ricevi una notifica. Il "principio" è abbastanza simile a come NSNotificationCenterfunziona, essendo il principale vantaggio che questo approccio funziona automaticamente anche a partire da iOS 6 (senza particolari modifiche come la necessità di pubblicare manualmente le notifiche).

Per il nostro UITextField-scenario funziona perfettamente se aggiungi questo codice, ad esempio, al tuo UIViewControllerche contiene il campo di testo:

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

}

Ringraziamo questa risposta relativa alla gestione del "contesto": https://stackoverflow.com/a/12097161/2078512

Nota: Sembra che mentre si è in procinto di modifica di un UITextFieldcon la tastiera integrata iOS, il "testo" di proprietà del campo di testo non viene aggiornato ad ogni nuova lettera digitata / rimosso. Al contrario, l'oggetto del campo di testo viene aggiornato "nel suo insieme" dopo aver rassegnato le dimissioni al primo stato di risposta del campo di testo.


5
Perché superare questo, basta usare il metodo di azione-bersaglio dalla risposta di XenElement. Se hai bisogno di decine di righe di codice, fai qualcosa che "dovrebbe" essere semplice, probabilmente stai sbagliando.
jrturton,

6
Sembrerebbe essere un comportamento previsto. Nessun altro metodo delegato (ad es. Scorrimento, selezione) viene chiamato per eventi originati dal codice.
jrturton,

1
Nota che UIKit non è garantito per essere conforme a KVO , quindi la soluzione KVO non funzionerà necessariamente.
Pang

@jrturton, riguardo alla tua domanda "perché", è del tutto normale che in qualche altra classe (forse una decorazione, un'animazione o simili) devi rispondere a ciò che sta accadendo nel campo di testo.
Fattie

27

Possiamo facilmente configurarlo da Storyboard, CTRL trascina l' @IBActionevento e cambia come segue:

inserisci qui la descrizione dell'immagine


14

Qui in versione rapida per lo stesso.

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

func textFieldDidChange(textField: UITextField) {

}

Grazie


12

Ho risolto il problema modificando il comportamento di shouldChangeChractersInRange. Se non restituisci NO, le modifiche non verranno applicate da iOS internamente, invece hai la possibilità di modificarle manualmente ed eseguire qualsiasi azione dopo le modifiche.

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

3
Il problema con questa soluzione è: la posizione del cursore viene impostata sulla FINE del campo (non appena si esegue textField.text = ...). Quindi, se l'utente desidera modificare i caratteri al centro del campo, quindi con ogni sequenza di tasti, il loro cursore si sposterà alla fine del campo. Provalo e vedi.
FranticRock,

Buon punto @Alex, ma semplice da aggirare. Basta calcolare il valore risultante in un NSString locale e passarlo al metodo didChangeTextInTextField: toText:, quindi restituire YES per consentire a iOS di eseguire l'aggiornamento.
jk7,

11

Versione Swift testata:

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

Parametri spiegati:

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

Quindi aggiungi il metodo che hai creato sopra nel tuo UIViewController:

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

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

}

7

Swift 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!)

}

5

Per Swift 3.0:

let textField = UITextField()

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

usando classe come:

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

    }
}

4

Versione Swift 3

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

E ottieni le modifiche qui

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

Spero che sia d'aiuto.


2

È necessario utilizzare la notifica per risolvere questo problema, poiché l'altro metodo ascolterà la casella di input non l'input effettivo, soprattutto quando si utilizza il metodo di input cinese. In viewDidload

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

poi

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

}

infine, corri, finirà.


2

Swift 3.1:

Selettore: ClassName.MethodName

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

  func fieldChanged(textfieldChange: UITextField){

    }

2

Versione 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

   }

}

Non dimenticare di impostare il delegato.


Ho 6 campi di testo nel mio controller di visualizzazione e voglio solo verificare se l'ultimo campo di testo è stato modificato stampa ("L'ultimo campo di testo è stato modificato!") Mi può aiutare?
Saeed Rahmatolahi,

Per prima cosa crea Iboutlet per il tuo textField. Quindi imposta il delegato e aggiungi Target come ho mostrato nella mia risposta. Nella funzione delegata textFieldDidChange, aggiungi questo: "if textField == yourLastTextField {print (" L'ultimo campo di testo è stato modificato! ")} In questo momento non ho accesso al mio computer. Renderò questo commento più leggibile quando arriva al mio computer
Joseph Francis il

2

Con chiusura:

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

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

e usando

textField.targetAction? = {
 // will fire on text changed
 }

2
  1. Le soluzioni KVO non funzionano

KVO NON funziona in iOS per i controlli: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

  1. Nella classe di osservazione, fai questo:

Dato che conosci la vista di testo che vuoi guardare:

var watchedTextView: UITextView!

Fai questo:

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

Tuttavia, fai attenzione:

  • è probabile che tu voglia chiamarlo solo una volta , quindi non chiamarlo, ad esempio,layoutSubviews

  • è abbastanza difficile sapere quando chiamarlo al meglio durante il processo di allevamento. Dipenderà dalla tua situazione. Sfortunatamente non esiste una soluzione standard bloccata

  • ad esempio di solito si può certamente non chiamare al initmomento, dato che ovviamentewatchedTextView non può esistere ancora

.

  1. il prossimo problema è che ...

Nessuno delle notifiche vengono chiamati quando il testo viene modificato a livello di codice .

Questa è una seccatura enorme, secolare e stupida nell'ingegneria di iOS.

I controlli semplicemente non lo fanno - fine della storia - richiamano le notifiche quando la proprietà .text viene modificata a livello di codice.

Questo è follemente fastidioso perché ovviamente - ovviamente - ogni app mai creata imposta il testo in modo programmatico, come cancellare il campo dopo i post degli utenti, ecc.

Devi sottoclassare la vista di testo (o un controllo simile) in questo modo:

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

    }

}

(Suggerimento: non dimenticare che la super chiamata deve venire prima ! La post chiamata.)

Non è disponibile alcuna soluzione, a meno che non si risolva il controllo eseguendo la sottoclasse come mostrato sopra. Questo è il unica soluzione.

Si noti che la notifica

UITextView.textDidChangeNotification

risultati in

func textViewDidChangeSelection(_ textView: UITextView) 

essere chiamato.

( Non textViewDidChange .)


1
[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(didChangeTextViewText:) 
name:UITextFieldTextDidChangeNotification object:nil];



- (void) didChangeTextViewText {
 //do something
}

0

Una cosa è che potresti avere più UITextFields. Quindi, dai loro un tag e quindi puoi attivare i tag. Ecco come impostare un osservatore in qualsiasi classe praticamente.

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

-1

Versione Swift 4

Utilizzo dell'osservazione valore-chiave Notifica agli oggetti le modifiche alle proprietà di altri oggetti.

var textFieldObserver: NSKeyValueObservation?

textFieldObserver = yourTextField.observe(\.text, options: [.new, .old]) { [weak self] (object, changeValue) in
  guard let strongSelf = self else { return }
  print(changeValue)
}

Buona idea. puoi avvolgerlo in un contesto e farcelo sapere?
Revanth Kausikan,

1
Questo è, sfortunatamente, totalmente sbagliato. KVO non funziona in iOS per i controlli: stackoverflow.com/a/6352525/1402846 developer.apple.com/library/archive/documentation/General/...
Fattie
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.