Evento de alteração de texto UITextField


563

Como posso detectar alterações de texto em um campo de texto? O método delegado shouldChangeCharactersInRangefunciona para algo, mas não atendeu exatamente minha necessidade. Como até retornar YES, os textos textField não estarão disponíveis para outros métodos de observação.

por exemplo, no meu código calculateAndUpdateTextFieldsnão recebeu o texto atualizado, o usuário digitou.

É sua maneira de obter algo como o textChangedmanipulador 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 é um bom exemplo de uma resposta SO com 1000 votos, que é completamente incorreta . O iOS é complicado, adequado e cheio de truques.
Fattie 20/01

Respostas:


1056

Da maneira correta de fazer a alteração de texto do uitextfield, ligue de volta :

Eu pego os caracteres enviados para um controle UITextField algo como isto:

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

No Objetivo-C:

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

Em Swift:

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

Em seguida, no textFieldDidChangemétodo, você pode examinar o conteúdo do campo de texto e recarregar sua exibição de tabela, conforme necessário.

Você pode usar isso e colocar o calculAndUpdateTextFields como seu selector.


18
Esta é a melhor solução - porque você também pode definir isso em Nibs ou Storyboards e não precisa escrever código UITextFieldTextDidChangeNotification em excesso
PostCodeism

3
O único problema é. Ele não funciona para mim junto com - (BOOL) textField: (UITextField *) textField shouldChangeCharactersInRange: (NSRange) range replaceString: (NSString *) string
iWheelBuy

4
@iWheelBuy Somente quando retornar NO, o que é lógico, porque quando você retorna NOdesse método, está basicamente dizendo que o texto no campo não deve mudar.
gitaarik

2
Isso é ótimo para editar alterações causadas. Ele não pega mudanças programáticas que ocorrem via: textField.text = "Some new value";. Existe uma maneira inteligente de entender isso?
Benjohn

10
Você teria pensado com da Apple UITextFieldDelegateque algo como func textField: UITextField, didChangeText text: Stringteria sido incluído, mas ... (dá caras na Apple um olhar sujo)
Brandon A

394

A resposta do XenElement é imediata.

O procedimento acima também pode ser feito no criador de interface, clicando com o botão direito do mouse no UITextField e arrastando o evento de envio "Editing Changed" para sua unidade de subclasse.

Evento de alteração UITextField


2
Pode ser fácil, mas depois de 10 a 15 minutos de pesquisa, eu não havia descoberto essa possibilidade até encontrar sua resposta por acaso. Tenha outro +1 de mim; é bom ter as soluções baseadas em código e IB altamente votadas em questões como esta.
Mark Amery

Acabei de verificar em 6.3 e está lá. Este é um UITextView, clique com o botão direito do mouse e veja "Edição Alterada".
William T.

4
por que diabos é chamado de edição alterada? louco! obrigado pela solução embora :-)
malhal

3
Como o evento é chamado durante a edição das alterações, enquanto o Valor Alterado acontece quando o campo de texto termina a edição, ou seja, renuncia à primeira resposta. UITextFieldTextDidChangeNotification é uma notificação UITextField, enquanto UIControlEventValueChanged é implementado por UIControl, que UIButton subclasses.
Camsoft 17/09/2015

2
Percebo isso não funcionar como guias do usuário para fora do campo de texto e um texto de AutoCorreção substitui o conteúdo
noobular

136

para definir o ouvinte de evento:

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

para realmente ouvir:

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

legal. Eu gosto dessa abordagem, porque gostaria de implementar o textField-change-listening em uma classe base ViewController que já é TextFieldDelegate. Dessa forma, posso adicionar Target: baseControllerMethod para (textField em subviews) como um encanto. VALEU!
HBublitz

87

Rápido:

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

Em seguida, implemente a função de retorno de chamada:

@objc func textFieldDidChange(textField: UITextField){

print("Text changed")

}

2
Trabalhou para mim, eu já havia tentado isso, mas não sabia que a função precisava de um argumento textField. Se isso não funcionar, verifique se o seu seletor possui um argumento para a passagem do campo de texto (mesmo que você não o use).
werm098

Adicionar _ antes textField na textFieldDidChange assim: func textFieldDidChange (textField: UITextField) {...}
Makalele

30

Conforme declarado aqui: evento de alteração de texto UITextField , parece que a partir do iOS 6 (iOS 6.0 e 6.1 marcado) não é possível detectar completamente as alterações nos UITextFieldobjetos apenas observando o UITextFieldTextDidChangeNotification.

Parece que apenas as alterações feitas diretamente pelo teclado interno do iOS são rastreadas agora. Isso significa que se você alterar seu UITextFieldobjeto apenas invocando algo como isto:, myUITextField.text = @"any_text"você não será notificado sobre nenhuma alteração.

Não sei se isso é um bug ou se destina. Parece um bug, pois não encontrei nenhuma explicação razoável na documentação. Isso também é afirmado aqui: Evento de alteração de texto UITextField .

Minha "solução" para isso é realmente postar uma notificação por mim mesmo para cada alteração que fizer no meu UITextField(se essa alteração for feita sem o uso do teclado iOS incorporado). Algo assim:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

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

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

Dessa forma, você está 100% confiante de que receberá a mesma notificação quando alterar a .textpropriedade do seuUITextField objeto, quando o atualizar "manualmente" em seu código ou por meio do teclado iOS incorporado.

É importante considerar que, como esse não é um comportamento documentado, essa abordagem pode levar a duas notificações recebidas pela mesma alteração em seu UITextFieldobjeto. Dependendo das suas necessidades (o que você realmente faz quando as UITextField.textalterações são feitas), isso pode ser um inconveniente para você.

Uma abordagem ligeiramente diferente seria postar uma notificação personalizada (ou seja, com um nome personalizado diferente de UITextFieldTextDidChangeNotification) se você realmente precisar saber se a notificação foi sua ou "feita pelo iOS".

EDITAR:

Acabei de encontrar uma abordagem diferente que eu acho que poderia ser melhor:

Isso envolve o recurso Observação de Valor-Chave (KVO) do Objective-C ( http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid / 10000177-BCICJDHA ).

Basicamente, você se registra como um observador de uma propriedade e, se essa propriedade mudar, você será notificado. O "princípio" é bastante semelhante ao modo como NSNotificationCenterfunciona, sendo a principal vantagem que essa abordagem funciona automaticamente também no iOS 6 (sem nenhum ajuste especial, como ter que postar notificações manualmente).

Para o nosso UITextFieldcenário, isso funciona muito bem se você adicionar esse código, por exemplo, o seu UIViewControllerque contém o 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];

}

Os créditos a esta resposta sobre o gerenciamento de "contexto": https://stackoverflow.com/a/12097161/2078512

Nota: Parece que enquanto você está no processo de edição de um UITextFieldcom o built-in teclado iOS, a propriedade "texto" do campo de texto não é atualizado a cada nova letra digitada / removido. Em vez disso, o objeto do campo de texto é atualizado "como um todo" após você renunciar ao status de primeiro respondedor do campo de texto.


5
Por que se expor a isso, basta usar o método de ação-alvo da resposta do XenElement. Se você precisar que dezenas de linhas de código façam algo que "deveria" ser simples, provavelmente está fazendo errado.
Jrturton

6
Esse parece ser o comportamento pretendido. Nenhum outro método delegado (por exemplo, rolagem, seleção) é chamado para eventos originados pelo código.
Jrturton

1
Observe que não é garantido que o UIKit seja compatível com KVO ; portanto, a solução KVO não funcionará necessariamente.
Pang

@jrturton, em relação à sua pergunta "por que", é completamente comum que em alguma outra classe (talvez uma decoração, animação ou algo semelhante) você precise responder ao que está acontecendo no campo de texto.
Fattie 20/01

27

Podemos facilmente configurar isso a partir de StoryboardCTRL, arraste o @IBActionevento e altere da seguinte maneira:

insira a descrição da imagem aqui


14

Aqui na versão rápida para o mesmo.

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

func textFieldDidChange(textField: UITextField) {

}

obrigado


12

Resolvi o problema alterando o comportamento de shouldChangeChractersInRange. Se você retornar NÃO, as alterações não serão aplicadas internamente pelo iOS; em vez disso, você terá a oportunidade de alterá-lo manualmente e executar quaisquer ações após as alterações.

- (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
O problema com esta solução é: A posição do cursor é definida como FIM do campo (assim que você faz textField.text = ...). Portanto, se o usuário quiser editar caracteres no meio do campo, a cada pressionamento de tecla, o cursor irá para o final do campo. Experimente e veja.
FranticRock

Bom ponto @Alex, mas simples de contornar. Apenas calcule o valor resultante em um NSString local e passe-o para o método didChangeTextInTextField: toText: e, em seguida, retorne YES para permitir que o iOS execute a atualização.
Jk7

11

Versão Swift testada:

//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

Em seguida, adicione o método que você criou acima em seu 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 o Swift 3.0:

let textField = UITextField()

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

usando classe como:

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

    }
}

4

Versão Swift 3

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

E obtenha as mudanças aqui

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

Espero que ajude.


2

Você deve usar a notificação para resolver esse problema, porque o outro método escutará a caixa de entrada e não a entrada realmente, especialmente quando você usar o método de entrada chinês. In viewDidload

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

então

- (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, você corre, será feito.


2

Swift 3.1:

Seletor: ClassName.MethodName

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

  func fieldChanged(textfieldChange: UITextField){

    }

2

Versão 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ão se esqueça de definir o delegado.


Eu tenho 6 campos de texto no meu controlador de exibição e só quero verificar se o último campo de texto foi alterado de impressão ("o último campo de texto foi alterado!") Você pode ajudar?
Saeed Rahmatolahi

Primeiro faça o Iboutlet para o seu campo de texto. Em seguida, defina o delegado e addTarget como mostrei na minha resposta. Na função delegada textFieldDidChange, adicione: "se textField == yourLastTextField {print (" o último campo de texto foi alterado! ")} No momento, não tenho acesso ao meu computador. Tornarei esse comentário mais legível quando eu chegar ao meu computador.
Joseph Francis

2

Com fechamento:

   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. As soluções KVO não funcionam

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

  1. Na classe de observação, você faz o seguinte:

Como você conhece a exibição de texto que deseja assistir:

var watchedTextView: UITextView!

Faça isso:

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

No entanto, tenha cuidado com isso:

  • é provável que você queira chamá-lo apenas uma vez . Portanto, não ligue, por exemplo,layoutSubviews

  • é muito difícil saber quando melhor chamá-lo durante o processo de abertura. Vai depender da sua situação. Infelizmente, não existe uma solução padrão bloqueada

  • por exemplo, você normalmente pode certamente não chamá-lo no inittempo, desde que, naturalmente, watchedTextViewpode não existir ainda

.

  1. o próximo problema é que ...

Nenhuma das notificações é chamada quando o texto é alterado programaticamente .

Esse é um incômodo enorme, antigo e estúpido na engenharia do iOS.

Os controles simplesmente não chamam as notificações quando a propriedade .text é alterada programaticamente.

Isso é incrivelmente irritante, porque é claro - obviamente - todos os aplicativos já criados definem o texto programaticamente, como limpar o campo depois que o usuário postar etc.

Você deve subclassificar a exibição de texto (ou controle semelhante) desta forma:

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

    }

}

(Dica - não se esqueça que a super chamada deve ocorrer antes ! A pós-chamada.)

Não solução disponível, a menos que você corrija o controle subclassificando conforme mostrado acima. Essa é a única solução.

Observe que a notificação

UITextView.textDidChangeNotification

resulta em

func textViewDidChangeSelection(_ textView: UITextView) 

sendo chamado.

( Não textViewDidChange .)


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



- (void) didChangeTextViewText {
 //do something
}

0

Uma coisa é que você pode ter vários UITextFields. Então, dê a eles uma tag e você poderá ativá-las. Veja como configurar um observador em qualquer 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

Versão Swift 4

Usando a observação do valor-chave Notificar objetos sobre alterações nas propriedades de outros objetos.

var textFieldObserver: NSKeyValueObservation?

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

Boa ideia. você pode agrupar isso em um contexto e nos informar?
Revanth Kausikan

1
Infelizmente, isso é totalmente errado. O KVO NÃO funciona no iOS para controles: stackoverflow.com/a/6352525/1402846 developer.apple.com/library/archive/documentation/General/…
Fattie
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.