Événement de modification de texte UITextField

563

Comment puis-je détecter des modifications de texte dans un textField? La méthode déléguée shouldChangeCharactersInRangefonctionne pour quelque chose, mais elle ne répondait pas exactement à mes besoins. Depuis jusqu'à ce qu'il renvoie OUI, les textes textField ne sont pas disponibles pour les autres méthodes d'observation.

par exemple dans mon code calculateAndUpdateTextFieldsn'a pas obtenu le texte mis à jour, l'utilisateur a tapé.

Est-ce leur moyen d'obtenir quelque chose comme textChangedun gestionnaire d'événements 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
la source
3
Ceci est un bon exemple d'une réponse SO avec 1000 votes qui est complètement incorrecte . iOS est délicat, simple et plein de gotchyas.
Fattie

Réponses:

1056

De la bonne façon de faire un rappel de changement de texte uitextfield :

J'attrape les caractères envoyés à un contrôle UITextField comme ceci:

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

Dans l'objectif-C:

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

Dans Swift:

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

Ensuite, dans la textFieldDidChangeméthode, vous pouvez examiner le contenu de textField et recharger votre vue tableau selon vos besoins.

Vous pouvez l' utiliser et de mettre calculateAndUpdateTextFields comme selector.

Daniel G. Wilson
la source
18
C'est la meilleure solution - car vous pouvez également définir cela dans Nibs ou Storyboards, et vous n'avez pas à écrire un code UITextFieldTextDidChangeNotification excessif
PostCodeism
3
Le seul problème est. Cela ne fonctionne pas pour moi avec - (BOOL) textField: (UITextField *) textField shouldChangeCharactersInRange: (NSRange) range replacementString: (NSString *) string
iWheelBuy
4
@iWheelBuy Seulement quand il revient NO, ce qui est logique, car lorsque vous revenez NOde cette méthode, vous dites essentiellement que le texte dans le champ ne devrait pas changer.
gitaarik
2
Ceci est idéal pour éditer les modifications provoquées. Il ne saisit pas les changements de programmation qui se produisent via: textField.text = "Some new value";. Existe-t-il un moyen intelligent pour attraper cela?
Benjohn
10
Vous auriez pensé avec Apple UITextFieldDelegateque quelque chose comme ça func textField: UITextField, didChangeText text: Stringaurait été inclus, mais ... (donne aux gars d'Apple un air sale)
Brandon A
394

La réponse de XenElement est parfaite.

Ce qui précède peut également être fait dans le générateur d'interface en cliquant avec le bouton droit sur UITextField et en faisant glisser l'événement d'envoi "Editing Changed" vers votre unité de sous-classe.

Événement de changement UITextField

William T.
la source
2
C'est facile, mais après 10 à 15 minutes de recherche, je n'avais découvert cette possibilité que par hasard en tombant sur votre réponse. Ayez un autre +1 de moi; il est agréable que les solutions basées sur le code et sur l'IB soient fortement votées sur des questions comme celle-ci.
Mark Amery
Je viens de le vérifier en 6.3 et il est là. Il s'agit d'un UITextView et faites un clic droit dessus et voyez "Modification modifiée".
William T.
4
pourquoi diable est-il nommé Editing Changed ?? furieux! merci pour la solution cependant :-)
malhal
3
Parce que l'événement est appelé lors de la modification des modifications, tandis que Value Changed se produit lorsque le champ de texte termine la modification, c'est-à-dire qu'il démissionne du premier répondeur. UITextFieldTextDidChangeNotification est une notification UITextField, tandis que UIControlEventValueChanged est implémentée par UIControl qui sous-classe UIButton.
Camsoft
2
Je remarque que cela ne fonctionne pas car les onglets utilisateur sortent du champ de texte et un texte de
correction automatique
136

pour définir l'écouteur d'événements:

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

écouter réellement:

- (void)textFieldDidChange:(UITextField *)textField {
    NSLog(@"text changed: %@", textField.text);
}
asdf
la source
cool. J'aime cette approche, car j'aimerais implémenter textField-change-listen dans une classe de base ViewController qui est déjà TextFieldDelegate. De cette façon, je peux ajouter Target: baseControllerMethod pour (textField dans les sous-vues) comme un charme. THX!
HBublitz
87

Rapide:

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

Ensuite, implémentez la fonction de rappel:

@objc func textFieldDidChange(textField: UITextField){

print("Text changed")

}
Juan Boero
la source
2
J'ai travaillé pour moi, j'avais déjà essayé cela mais je ne savais pas que la fonction avait besoin d'un argument textField. Si cela ne fonctionne pas pour vous, assurez-vous de vérifier que votre sélecteur a un argument pour que textField soit passé (même si vous ne l'utilisez pas).
werm098
Ajoutez _ avant textField dans textFieldDidChange comme ceci: func textFieldDidChange (textField: UITextField) {...}
Makalele
30

Comme indiqué ici: Événement de changement de texte UITextField , il semble qu'à partir d'iOS 6 (iOS 6.0 et 6.1 vérifié), il n'est pas possible de détecter complètement les changements dans les UITextFieldobjets simplement en observant le UITextFieldTextDidChangeNotification.

Il semble que seules les modifications apportées directement par le clavier iOS intégré soient suivies maintenant. Cela signifie que si vous changez votre UITextFieldobjet simplement en invoquant quelque chose comme ceci:, myUITextField.text = @"any_text"vous ne serez pas du tout informé des changements.

Je ne sais pas si c'est un bug ou si c'est prévu. Cela ressemble à un bug car je n'ai trouvé aucune explication raisonnable dans la documentation. Ceci est également indiqué ici: événement de changement de texte UITextField .

Ma «solution» à cela consiste à publier une notification par moi-même pour chaque modification que j'apporte à mon UITextField(si cette modification est effectuée sans utiliser le clavier iOS intégré). Quelque chose comme ça:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

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

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

De cette façon, vous êtes sûr à 100% que vous recevrez la même notification lorsque vous modifiez la .textpropriété de votre UITextFieldobjet, soit lorsque vous le mettez à jour "manuellement" dans votre code, soit via le clavier iOS intégré.

Il est important de considérer que, comme ce n'est pas un comportement documenté, cette approche peut conduire à 2 notifications reçues pour le même changement dans votre UITextFieldobjet. Selon vos besoins (ce que vous faites réellement lors de vos UITextField.textchangements), cela pourrait vous être gênant.

Une approche légèrement différente consisterait à publier une notification personnalisée (c'est-à-dire avec un nom personnalisé autre que UITextFieldTextDidChangeNotification) si vous avez réellement besoin de savoir si la notification était la vôtre ou "conçue par iOS".

ÉDITER:

Je viens de trouver une approche différente qui, je pense, pourrait être meilleure:

Cela implique la fonctionnalité Key-Value Observing (KVO) d'Objective-C ( http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid / 10000177-BCICJDHA ).

Fondamentalement, vous vous enregistrez en tant qu'observateur d'une propriété et si cette propriété change, vous en êtes informé. Le "principe" est assez similaire à la façon dont NSNotificationCenterfonctionne, étant le principal avantage que cette approche fonctionne automatiquement aussi à partir d'iOS 6 (sans aucun ajustement spécial comme devoir publier manuellement des notifications).

Pour notre UITextFieldscénario, cela fonctionne très bien si vous ajoutez ce code, par exemple, à votre UIViewControllerqui contient le champ de texte:

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

}

Crédit à cette réponse concernant la gestion du "contexte": https://stackoverflow.com/a/12097161/2078512

Remarque: Il semble que lorsque vous êtes en train de modifier un UITextFieldavec le clavier iOS intégré, la propriété "texte" du champ de texte n'est pas mise à jour à chaque nouvelle lettre tapée / supprimée. Au lieu de cela, l'objet de champ de texte est mis à jour "dans son ensemble" après avoir démissionné du statut de premier répondant du champ de texte.

ercolemtar
la source
5
Pourquoi vous mettre à travers cela, utilisez simplement la méthode cible-action de la réponse de XenElement. Si vous avez besoin de dizaines de lignes de code pour faire quelque chose qui "devrait" être simple, vous vous trompez probablement.
jrturton
6
Cela semblerait être un comportement voulu. Aucune autre méthode déléguée (par exemple, défilement, sélection) n'est appelée pour les événements générés par le code.
jrturton
1
Notez qu'UIKit n'est pas garanti conforme à KVO , donc la solution KVO ne fonctionnera pas nécessairement.
Pang
@jrturton, en ce qui concerne votre question "pourquoi", il est tout à fait courant que dans une autre classe (peut-être une décoration, une animation ou similaire), vous devez répondre à ce qui se passe dans le champ de texte.
Fattie
27

Nous pouvons facilement configurer cela à partir de Storyboard, CTRL faites glisser l' @IBActionévénement et modifiez-le comme suit:

entrez la description de l'image ici

bikram sapkota
la source
14

Ici en version rapide pour les mêmes.

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

func textFieldDidChange(textField: UITextField) {

}

Merci

hindou
la source
12

J'ai résolu le problème de modification du comportement de shouldChangeChractersInRange. Si vous retournez NON, les modifications ne seront pas appliquées par iOS en interne, mais vous avez la possibilité de le modifier manuellement et d'effectuer des actions après les modifications.

- (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
la source
3
Le problème avec cette solution est: la position du curseur est définie à la fin du champ (dès que vous effectuez textField.text = ...). Donc, si l'utilisateur veut modifier des caractères au milieu du champ, à chaque frappe, son curseur ira à la fin du champ. Essayez-le et voyez.
FranticRock
Bon point @Alex, mais simple à contourner. Calculez simplement la valeur résultante dans une NSString locale et transmettez-la à votre méthode didChangeTextInTextField: toText:, puis retournez YES pour laisser iOS effectuer la mise à jour.
jk7
11

Version Swift testée:

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

Explication des paramètres:

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

Ajoutez ensuite la méthode que vous avez créée ci-dessus dans votre UIViewController:

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

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

}
fantôme kemicofa
la source
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!)

}
acidgate
la source
5

Pour Swift 3.0:

let textField = UITextField()

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

en utilisant une classe comme:

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

    }
}
pedrouan
la source
4

Version Swift 3

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

Et obtenez les changements ici

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

J'espère que cela aide.

Riajur Rahman
la source
2

Vous devez utiliser la notification pour résoudre ce problème, car l'autre méthode écoutera la zone de saisie et non l'entrée réelle, en particulier lorsque vous utilisez la méthode de saisie chinoise. In viewDidload

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

puis

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

}

enfin, vous courez, c'est fait.

HsuChihYung
la source
2

Swift 3.1:

Sélecteur: ClassName.MethodName

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

  func fieldChanged(textfieldChange: UITextField){

    }
Beyaz
la source
2

Version 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

   }

}

N'oubliez pas de définir le délégué.

Joseph Francis
la source
J'ai 6 champs de texte dans mon contrôleur de vue et je veux juste vérifier si le dernier champ de texte a été modifié imprimer ("le dernier champ de texte a été modifié!") Pouvez-vous m'aider?
Saeed Rahmatolahi
Créez d'abord Iboutlet pour votre textField. Ensuite, définissez le délégué et addTarget comme je l'ai montré dans ma réponse. Dans la fonction déléguée textFieldDidChange, ajoutez ceci: "si textField == yourLastTextField {print (" le dernier champ de texte a été modifié! ")} Pour l'instant, je n'ai pas accès à mon ordinateur. Je rendrai ce commentaire plus lisible lorsque je accéder à mon ordinateur.
Joseph Francis
2

Avec fermeture:

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

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

et en utilisant

textField.targetAction? = {
 // will fire on text changed
 }
ebohdas
la source
2
  1. Les solutions KVO ne fonctionnent pas

KVO ne fonctionne PAS dans iOS pour les contrôles: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

  1. Dans la classe d'observation, vous faites ceci:

Étant donné que vous connaissez la vue texte que vous souhaitez regarder:

var watchedTextView: UITextView!

Faites ceci:

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

Cependant, soyez prudent avec cela:

  • il est probable que vous ne souhaitiez l'appeler qu'une seule fois , alors ne l'appelez pas, par exemple,layoutSubviews

  • il est assez difficile de savoir quand l'appeler au mieux pendant votre processus de rappel. Cela dépendra de votre situation. Malheureusement, il n'existe pas de solution standard verrouillée

  • par exemple, vous ne pouvez généralement pas l' appeler à inittemps, car il se watchedTextViewpeut bien sûr qu'il n'existe pas encore

.

  1. le prochain problème est que ...

Aucune des notifications n'est appelée lorsque le texte est modifié par programme .

C'est une énorme nuisance séculaire et stupide dans l'ingénierie iOS.

Les contrôles n'appellent tout simplement pas - fin de l'histoire - les notifications lorsque la propriété .text est modifiée par programme.

Ceci est incroyablement ennuyeux car bien sûr - évidemment - chaque application jamais créée définit le texte par programme, comme effacer le champ après les messages de l'utilisateur, etc.

Vous devez sous-classer la vue texte (ou un contrôle similaire) comme ceci:

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

    }

}

(Astuce - n'oubliez pas que le super appel doit venir avant ! Le post-appel.)

Il n'y a pas de solution disponible, sauf si vous corrigez le contrôle en sous-classant comme indiqué ci-dessus. C’est la seule solution.

Notez que la notification

UITextView.textDidChangeNotification

résulte en

func textViewDidChangeSelection(_ textView: UITextView) 

être appelé.

( Non textViewDidChange .)

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



- (void) didChangeTextViewText {
 //do something
}
PeiweiChen
la source
0

Une chose est que vous pouvez avoir plusieurs UITextFields. Alors, donnez-leur une balise et ensuite vous pouvez activer les balises. Voici comment configurer un observateur dans n'importe quelle classe à peu près.

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
la source
-1

Version Swift 4

Utilisation de l'observation des valeurs-clés Informez les objets des modifications apportées aux propriétés des autres objets.

var textFieldObserver: NSKeyValueObservation?

textFieldObserver = yourTextField.observe(\.text, options: [.new, .old]) { [weak self] (object, changeValue) in
  guard let strongSelf = self else { return }
  print(changeValue)
}
Valeriy
la source
Bonne idée. pouvez-vous en parler dans un contexte et nous le faire savoir?
Revanth Kausikan
1
C'est malheureusement totalement faux. KVO ne fonctionne PAS dans iOS pour les contrôles: stackoverflow.com/a/6352525/1402846 developer.apple.com/library/archive/documentation/General/…
Fattie