Evento de cambio de texto de UITextField


563

¿Cómo puedo detectar cualquier cambio de texto en un campo de texto? El método delegado shouldChangeCharactersInRangefunciona para algo, pero no satisfizo exactamente mi necesidad. Como hasta que devuelve SÍ, los textos textField no están disponibles para otros métodos de observación.

Por ejemplo, en mi código calculateAndUpdateTextFieldsno recibí el texto actualizado, el usuario ha escrito.

Es su forma de obtener algo como el textChangedcontrolador de eventos 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
Este es un buen ejemplo de una respuesta SO con 1000 votos que es completamente incorrecta . iOS es complicado, adecuado y está lleno de gotchyas.
Fattie

Respuestas:


1056

De la manera correcta de hacer un cambio de texto uitextfield, devuelva la llamada :

Capturo los caracteres enviados a un control UITextField algo como esto:

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

En el objetivo-C:

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

En Swift:

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

Luego, en el textFieldDidChangemétodo, puede examinar el contenido de textField y volver a cargar la vista de tabla según sea necesario.

Podría usar eso y poner CalculateAndUpdateTextFields como su selector.


18
Esta es la mejor solución - porque también se puede configurar esto en plumas o guiones gráficos, y no tener que escribir código UITextFieldTextDidChangeNotification excesiva
PostCodeism

3
El único problema es No funciona para mí junto con - (BOOL) textField: (UITextField *) textField shouldChangeCharactersInRange: (NSRange) range replaceString: (NSString *) string
iWheelBuy

44
@iWheelBuy Solo cuando regresa NO, lo cual es lógico, porque cuando regresas NOde este método, básicamente estás diciendo que el texto en el campo no debería cambiar.
gitaarik

2
Esto es genial para editar cambios causados. No coge cambios programáticos que se producen a través de: textField.text = "Some new value";. ¿Hay alguna manera inteligente de atrapar esto?
Benjohn

10
Habría pensado con Apple UITextFieldDelegateque algo así func textField: UITextField, didChangeText text: Stringse habría incluido, pero ... (le da a los chicos de Apple una mirada sucia)
Brandon A

394

La respuesta de XenElement es acertada.

Lo anterior también se puede hacer en el generador de interfaces haciendo clic derecho en UITextField y arrastrando el evento de envío "Edición modificada" a su unidad de subclase.

Evento de cambio UITextField


2
Puede ser fácil, pero después de 10-15 minutos de búsqueda, no había descubierto esta posibilidad hasta que me topé por casualidad con su respuesta. Ten otro +1 de mi parte; Es bueno tener tanto las soluciones basadas en código como las basadas en IB altamente votadas en preguntas como esta.
Mark Amery

Acabo de comprobarlo en 6.3 y está ahí. Este es un UITextView y haga clic derecho sobre él y vea "Edición modificada".
William T.

44
¿Por qué demonios se llama Editing Changed? ¡enojado! gracias por la solución :-)
malhal

3
Debido a que el evento se llama cuando se editan los cambios, mientras que Value Changed ocurre cuando el campo de texto termina de editar, es decir, renuncia al primer respondedor. UITextFieldTextDidChangeNotification es una notificación UITextField, mientras que UIControlEventValueChanged es implementado por UIControl que subclases UIButton.
Camsoft

2
Noté que esto no funciona cuando las pestañas de los usuarios salen del campo de texto y un texto de autocorrección reemplaza el contenido
noobular

136

para configurar el oyente del evento:

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

para escuchar realmente:

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

frio. Me gusta este enfoque, porque me gustaría implementar textField-change-listening en una clase base ViewController que ya es TextFieldDelegate. De esta manera puedo agregarTarget: baseControllerMethod para (textField en subvistas) como un encanto. ¡GRACIAS!
HBublitz

87

Rápido:

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

Luego, implemente la función de devolución de llamada:

@objc func textFieldDidChange(textField: UITextField){

print("Text changed")

}

2
Funcionó para mí, ya lo había intentado pero no me di cuenta de que la función necesitaba un argumento textField. Si esto no funciona para usted, asegúrese de verificar que su selector tenga un argumento para que se pase el campo de texto (incluso si no lo usa).
werm098

Agregue _ antes de textField en textFieldDidChange de esta manera: func textFieldDidChange (textField: UITextField) {...}
Makalele

30

Como se indica aquí: evento de cambio de texto UITextField , parece que a partir de iOS 6 (iOS 6.0 y 6.1 verificado) no es posible detectar completamente los cambios en los UITextFieldobjetos simplemente observando el UITextFieldTextDidChangeNotification.

Parece que ahora solo se rastrean los cambios realizados directamente por el teclado iOS incorporado. Esto significa que si cambia su UITextFieldobjeto simplemente invocando algo como esto: myUITextField.text = @"any_text"no se le notificará ningún cambio.

No sé si esto es un error o si está destinado. Parece un error ya que no he encontrado ninguna explicación razonable en la documentación. Esto también se indica aquí: evento de cambio de texto UITextField .

Mi "solución" para esto es publicar una notificación por mí mismo por cada cambio que realice en mi UITextField(si ese cambio se realiza sin usar el teclado iOS incorporado). Algo como esto:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

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

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

De esta manera, está 100% seguro de que recibirá la misma notificación cuando cambie la .textpropiedad de suUITextField objeto, ya sea cuando lo actualice "manualmente" en su código o mediante el teclado iOS incorporado.

Es importante tener en cuenta que, dado que este no es un comportamiento documentado, este enfoque puede dar lugar a 2 notificaciones recibidas para el mismo cambio en su UITextFieldobjeto. Dependiendo de sus necesidades (lo que realmente hace cuando UITextField.textcambia), esto podría ser un inconveniente para usted.

Un enfoque ligeramente diferente sería publicar una notificación personalizada (es decir, con un nombre personalizado diferente a UITextFieldTextDidChangeNotification) si realmente necesita saber si la notificación fue suya o "hecha por iOS".

EDITAR:

Acabo de encontrar un enfoque diferente que creo que podría ser mejor:

Esto implica la característica de Observación de valores clave (KVO) de Objective-C ( http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid / 10000177-BCICJDHA ).

Básicamente, usted se registra como observador de una propiedad y si esta propiedad cambia, se le notificará al respecto. El "principio" es bastante similar a cómo NSNotificationCenterfunciona, siendo la principal ventaja de que este enfoque funciona automáticamente también a partir de iOS 6 (sin ningún ajuste especial como tener que publicar notificaciones manualmente).

Para nuestro UITextField-scenario, esto funciona bien si agrega este código a, por ejemplo, el UIViewControllerque contiene el campo de texto:

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édito a esta respuesta con respecto a la gestión del "contexto": https://stackoverflow.com/a/12097161/2078512

Nota: Parece que mientras está en el proceso de editar un UITextFieldcon el teclado iOS incorporado, la propiedad "texto" del campo de texto no se actualiza con cada nueva letra escrita / eliminada. En cambio, el objeto de campo de texto se actualiza "en su conjunto" después de que renuncia al primer estado de respuesta del campo de texto.


55
¿Por qué pasar por esto? Simplemente use el método de acción objetivo de la respuesta de XenElement. Si necesita decenas de líneas de código, haga algo que "debería" ser simple, probablemente lo esté haciendo mal.
jrturton

66
Eso parece ser un comportamiento intencionado. Ningún otro método de delegado (por ejemplo, desplazamiento, selección) se llama para eventos originados por código.
jrturton

1
Tenga en cuenta que no se garantiza que UIKit sea compatible con KVO , por lo que la solución KVO no necesariamente funcionará.
Pang

@jrturton, con respecto a su pregunta de "por qué", es completamente común que en alguna otra clase (tal vez una decoración, animación o similar) necesite responder a lo que está sucediendo en el campo de texto.
Fattie

27

Podemos configurarlo fácilmente Storyboard, arrastrar CTRL @IBActiony cambiar el evento de la siguiente manera:

ingrese la descripción de la imagen aquí


14

Aquí en versión rápida para lo mismo.

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

func textFieldDidChange(textField: UITextField) {

}

Gracias


12

Resolví el problema cambiando el comportamiento de shouldChangeChractersInRange. Si devuelve NO, iOS no aplicará los cambios internamente, sino que tiene la oportunidad de cambiarlos manualmente y realizar cualquier acción después de los cambios.

- (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
El problema con esta solución es: La posición del cursor se establece al FINAL del campo (tan pronto como lo haga textField.text = ...). Entonces, si el usuario desea editar caracteres en el medio del campo, con cada pulsación de tecla, su cursor irá al final del campo. Pruébalo y verás.
FranticRock

Buen punto @Alex, pero sencillo de solucionar. Simplemente calcule el valor resultante en un NSString local y páselo a su método didChangeTextInTextField: toText: luego devuelva SÍ para permitir que iOS realice la actualización.
jk7

11

Versión Swift probada:

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

Parámetros explicados:

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

Luego agregue el método que creó anteriormente en su 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

Para Swift 3.0:

let textField = UITextField()

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

usando clase como:

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

    }
}

4

Versión Swift 3

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

Y obtén los cambios aquí

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

Espero eso ayude.


2

Debe usar la notificación para resolver este problema, porque el otro método escuchará el cuadro de entrada, no la entrada real, especialmente cuando usa el método de entrada chino. En vista Descarga

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

entonces

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

}

finalmente, corres, listo.


2

Swift 3.1:

Selector: ClassName.MethodName

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

  func fieldChanged(textfieldChange: UITextField){

    }

2

Versión 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

   }

}

No olvide configurar el delegado.


Tengo 6 campos de texto en mi controlador de vista y solo quiero verificar si el último campo de texto se ha cambiado imprimir ("¡se ha cambiado el último campo de texto!") ¿Pueden ayudarme?
Saeed Rahmatolahi

Primero haga Iboutlet para su textField. Luego configure el delegado y agregueTarget como mostré en mi respuesta. En la función de delegado textFieldDidChange, agregue esto: "if textField == yourLastTextField {print (" ¡se modificó el último campo de texto! ")} En este momento no tengo acceso a mi computadora. Haré que este comentario sea más legible cuando llegar a mi computadora.
Joseph Francis

2

Con cierre:

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

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

y usando

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

2
  1. Las soluciones de KVO no funcionan

KVO NO funciona en iOS para controles: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

  1. En la clase de observación, haces esto:

Dado que conoce la vista de texto que desea ver:

var watchedTextView: UITextView!

Hacer esto:

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

Sin embargo, tenga cuidado con eso:

  • es probable que solo quieras llamar así una vez , así que no lo llames, por ejemplo,layoutSubviews

  • es bastante difícil saber cuándo llamarlo mejor durante el proceso de actualización Dependerá de tu situación. Lamentablemente, no existe una solución estándar bloqueada

  • por ejemplo, por lo general, no puede llamarlo a inittiempo, ya que, por supuesto watchedTextView, aún no existe

.

  1. el siguiente problema es que ...

Ninguna de las notificaciones se llama cuando el texto se cambia mediante programación .

Esta es una molestia enorme, antigua y estúpida en la ingeniería de iOS.

Los controles simplemente no, al final de la historia , llaman a las notificaciones cuando la propiedad .text se cambia mediante programación.

Esto es increíblemente molesto porque, por supuesto, obviamente, cada aplicación creada establece el texto mediante programación, como borrar el campo después de que el usuario publica, etc.

Debe subclasificar la vista de texto (o control similar) de esta manera:

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

    }

}

(Consejo: ¡no olvide que la súper llamada tiene que venir antes ! La llamada posterior).

No hay una solución disponible, a menos que arregle el control subclasificando como se muestra arriba. Ese es el unico solución.

Tenga en cuenta que la notificación

UITextView.textDidChangeNotification

resultados en

func textViewDidChangeSelection(_ textView: UITextView) 

siendo llamado.

( No textViewDidChange )


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



- (void) didChangeTextViewText {
 //do something
}

0

Una cosa es que puede tener múltiples UITextFields. Entonces, deles una etiqueta y luego puede encender las etiquetas. Aquí se explica cómo configurar un observador en cualquier clase.

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

Versión Swift 4

Uso de la observación de valores clave Notifique a los objetos sobre los cambios en las propiedades de otros objetos.

var textFieldObserver: NSKeyValueObservation?

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

Buena idea. ¿Puedes envolver esto en un contexto y hacernos saber?
Revanth Kausikan

1
Esto, desafortunadamente, es totalmente incorrecto. KVO NO funciona en iOS para controles: stackoverflow.com/a/6352525/1402846 developer.apple.com/library/archive/documentation/General/…
Fattie
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.