Verwenden Sie KVO für NSTextFields, die miteinander verbunden sind

7

Ich habe Probleme, KVO mit Textfeldern zu arbeiten, die in einer Cocoa-App miteinander verbunden sind. Ich habe das zum Funktionieren gebracht, wenn ich Zeichenketten in NSTextFields mit Knöpfen einstelle, aber es arbeitet nicht mit Bindungen. Wie immer wird jede Hilfe von Stack Overflow sehr geschätzt.

Zweck meines Codes ist:

  • binden mehrere Textfelder zusammen

  • Wenn eine Zahl in ein Feld eingegeben wird, müssen die anderen Felder automatisch aktualisiert werden

  • Beobachten Sie die Änderungen in den Textfeldern

Hier ist mein Code für MainClass, der eine NSObject-Unterklasse ist:

%Vor%

Und hier ist die Bindung für eines der Textfelder:

    
wigging 01.11.2012, 02:41
quelle

1 Antwort

31

Schlüsselwertbeobachtung auf NSTextFields

In Ihrer -awakeFromNib -Methode haben Sie

geschrieben %Vor%

Dies geht nicht so, wie Sie es sich erhoffen: self.fieldA ist nicht key-value Codierung kompatibel für den Schlüssel numA : wenn Sie versuchen, -valueForKey: oder% co_de zu senden % mit dem Schlüssel -setValue:forKey: bis @"numA" , erhalten Sie die folgenden Ausnahmen:

  

[valueForUndefinedKey:]: Diese Klasse ist für den Schlüssel numA nicht schlüsselcodefähig.

und

  

[setValue: forUndefinedKey:]: Diese Klasse ist für den Schlüssel numA nicht schlüsselcodefähig.

Als Ergebnis , die self.fieldA Instanzen sind nicht Schlüsselwert beobachtend kompatibel für NSTextField , entweder: Die erste Anforderung, die für einen Schlüssel KVO-konform sein muss, ist KVC-konform für diesen Schlüssel.

Es ist jedoch KVO-konform unter anderem für @"numA" . Dies ermöglicht es Ihnen, was ich bereits beschrieben .

Hinweis : Dies ändert sich nicht durch die Art und Weise, wie Sie Bindungen in Interface Builder eingerichtet haben. Mehr dazu später.

Das Problem mit der Schlüsselwertbeobachtung im stringValue von NSTextField

Das Beobachten eines stringValue Wertes für NSTextField funktioniert, wenn @"stringValue" für -setStringValue: aufgerufen wird. Dies ist ein Ergebnis der Interna von KVO.

Eine kurze Reise in KVO Internals

Wenn Sie beginnen, einen Schlüsselwert zu beobachten, der ein Objekt zum ersten Mal beobachtet, wird die Klasse des Objekts geändert - sein NSTextField -Zeiger wird geändert. Sie können dies beobachten, indem Sie isa

überschreiben %Vor%

Im Allgemeinen ändert sich der Name der Klasse von -addObserver:forKeyPath:options:context: in Object .

Wenn wir NSKVONotifying_Object für eine Instanz von -addObserver:forKeyPath:options:context: mit dem Schlüsselpfad Object aufgerufen hätten - ein Schlüssel, für den Instanzen von @"property" KVC-konform sind - rufen wir als nächstes Object auf Unsere Instanz von -setProperty: (tatsächlich eine Instanz von Object ), die folgenden Nachrichten werden an das Objekt

gesendet
  1. NSKVONotifying_Object passing -willChangeValueForKey:
  2. @"property" passing -setProperty:
  3. @"property" passing -didChangeValueForKey:

Der Bruch innerhalb einer dieser Methoden zeigt, dass sie von der undokumentierten Funktion @"property" aufgerufen werden.

Die Relevanz all dessen ist, dass die Methode _NSSetObjectValueAndNotify für den Beobachter aufgerufen wird, den wir unserer Instanz -observeValueForKeyPath:ofObject:change:context: für den Schlüsselpfad Object von @"property" hinzugefügt haben. Hier ist der Anfang des Stack-Trace:

%Vor%

In welcher Beziehung steht dies zu -didChangeValueForKey: und NSTextField ?

In Ihrem vorherigen Setup waren Sie Hinzufügen eines Beobachters zu Ihrem Textfeld in @"stringValue" . Das bedeutet, dass Ihr Textfeld bereits eine Instanz von -awakeFromNib war.

Sie würden dann die eine oder andere Taste drücken, die wiederum NSKVONotifying_NSTextField in Ihrem Textfeld aufrufen würde. Sie konnten diese Änderung beobachten, weil - als eine Instanz von -setStringValue - Ihr Textfeld beim Empfang von NSKVONotifying_NSTextField tatsächlich

empfangen wurde
  1. setStringValue:value
  2. willChangeValueForKey:@"stringValue"
  3. setStringValue:value

Wie oben wird innerhalb von didChangeValueForKey:@"stringValue" allen Objekten, die den Wert des Textfelds für didChangeValueForKey:@"stringValue" beobachten, mitgeteilt, dass sich der Wert für diesen Schlüssel in ihren eigenen Implementierungen von @"stringValue" geändert hat. Dies gilt insbesondere für das Objekt, das Sie als Beobachter für das Textfeld in -observeValueForKeyPath:ofObject:change:context: hinzugefügt haben.

Zusammenfassend konnten Sie die Änderung des Textfeldwerts für -awakeFromNib beobachten, weil Sie sich selbst als Beobachter des Textfelds für diesen Schlüssel hinzugefügt haben, und , weil @"stringValue" für das Textfeld aufgerufen wurde .

Also, was ist das Problem?

Bisher unter dem Deckmantel der Diskussion "Das Problem mit der Schlüsselwertbeobachtung auf NSTextFields" haben wir nur den ersten Satz sinnvoll gemacht

  

Das Beobachten eines -setStringValue Wertes für NSTextField funktioniert, wenn @"stringValue" für -setStringValue: aufgerufen wird.

Und das klingt großartig! Also, was ist das Problem?

Das Problem ist, dass NSTextField nicht auf dem Textfeld aufgerufen wird, während der Benutzer es eingibt oder auch nachdem der Benutzer die Bearbeitung beendet hat (z. B. durch Ausblenden aus dem Textfeld). (Außerdem werden -setStringValue: und -willChangeValueForKey: nicht manuell aufgerufen. Wenn dies der Fall wäre, würde unsere KVO funktionieren; tut es aber nicht.) Dies bedeutet, dass unsere KVO auf -didChangeValueForKey: funktioniert, wenn @"stringValue" auf der Im Textfeld funktioniert NICHT , wenn der Benutzer selbst Text eingibt.

TL; DR : KVO auf -setStringValue: von @"stringValue" ist nicht gut genug, da es nicht für Benutzereingaben funktioniert.

Bindung eines NSTextField-Wertes an einen String

Versuchen wir es mit Bindings.

Ersteinrichtung

Erstellen Sie ein Beispielprojekt mit einem separaten Fenstercontroller (ich habe den Creative-Namen NSTextField verwendet) mit XIB. ( Hier ist das Projekt, bei dem ich auf GitHub beginne. ) In WindowController hat eine Eigenschaft WindowController.m in einer Klassenerweiterung hinzugefügt:

%Vor%

Erstellen Sie in Interface Builder ein Textfeld und öffnen Sie den Bindungsinspektor:

Erweitern Sie unter der Überschrift "Wert" den Wert "Wert":

Die Popup-Schaltfläche neben dem Kontrollkästchen "Bind to" (Bindet an) hat derzeit "Shared User Defaults Controller" ausgewählt. Wir möchten den Wert des Textfelds an unsere stringA -Instanz binden. Wählen Sie stattdessen "Dateibesitzer". Wenn dies geschieht, wird das Feld "Controller Key" geleert und das Feld "Model Key Path" wird in "self" geändert.

Wir möchten den Wert dieses Textfelds an die WindowController -Instanzeneigenschaft WindowController binden, also den "Modellschlüsselpfad" in stringA :

ändern

An diesem Punkt sind wir fertig. ( Bisheriger Fortschritt bei GitHub. ) Wir haben den Wert des Textfelds erfolgreich an die Eigenschaft self.stringA gebunden %Code%.

Testen Sie es

Wenn wir WindowController auf einen Wert in -init setzen, wird dieser Wert beim Laden des Fensters im Textfeld angezeigt:

%Vor%

Und schon haben wir Bindungen in die andere Richtung eingerichtet; Nach Beendigung der Bearbeitung im Textfeld wird die Eigenschaft stringA des Fenstercontrollers gesetzt. Wir können dies überprüfen, indem wir den Setter überschreiben:

%Vor%

Beantworte Hazy, versuche es noch einmal

Nachdem Sie einen Text in das Textfeld eingegeben und die Tabulatortaste gedrückt haben, sehen wir, dass

ausgedruckt wird %Vor%

Das sieht gut aus. Warum haben wir die ganze Zeit nicht darüber gesprochen ??? Es ist ein bisschen ein Problem hier: die lästige Drücken Tab Ding. Wenn Sie den Wert eines Textfelds an eine Zeichenfolge binden, wird der Zeichenfolgenwert erst festgelegt, wenn die Bearbeitung im Textfeld beendet ist.

Eine neue Hoffnung

Aber es gibt noch Hoffnung! Die Kakao-Bindungs-Dokumentation für stringA gibt diesen an Bindeoption für stringA ist NSTextField . Und siehe da, es gibt ein Kontrollkästchen, das genau dieser Option im Bindungsinspektor für NSTextFields Wert entspricht. Gehen Sie weiter und überprüfen Sie diese Box.

Mit dieser Änderung, während wir Dinge eingeben, wird die Aktualisierung der Eigenschaft NSTextField des Fenster-Controllers fortlaufend ausgeloggt:

%Vor%

Schließlich aktualisieren wir fortlaufend die Zeichenfolge des Fenstercontrollers aus dem Textfeld. Der Rest ist einfach. Fügen Sie dem Fenster ein paar weitere Textfelder hinzu, binden Sie sie an stringA und legen Sie fest, dass sie fortlaufend aktualisiert werden. Sie haben zu diesem Zeitpunkt drei synchronisierte NSContinuouslyUpdatesValueBindingOption s! Hier ist das Projekt mit drei synchronisierten Textfeldern.

Der Rest des Weges

Sie möchten drei Textfelder einrichten, die Zahlen anzeigen, die zueinander in Beziehung stehen. Da wir uns jetzt mit Zahlen beschäftigen, entfernen wir die Eigenschaft stringA von NSTextField und ersetzen sie durch stringA , WindowController und numberA :

%Vor%

Als Nächstes binden wir das erste Textfeld an die NummerA des Eigentümers der Datei, die zweite an NummerB und so weiter. Schließlich müssen wir nur eine Eigenschaft hinzufügen, die die Menge ist, die auf diese verschiedenen Arten dargestellt wird. Nennen wir diesen Wert numberB .

%Vor%

Wir brauchen die konstanten Umrechnungsfaktoren, um von den Einheiten von numberC in die Einheiten quantity usw. zu transformieren, also fügen Sie

hinzu %Vor%

(Verwenden Sie natürlich die Zahlen, die für Ihre Situation relevant sind.) Damit können wir die Accessoren für jede der Zahlen implementieren:

%Vor%

All die verschiedenen Access-Nummern sind jetzt nur indirekte Mechanismen für den Zugriff auf quantity und eignen sich perfekt für Bindungen. Es gibt nur noch eine zusätzliche Sache, die noch erledigt werden muss: Wir müssen sicherstellen, dass die Beobachter alle Zahlen neu aufwerten, wenn numberA geändert wird:

%Vor%

Wenn Sie nun in eines der Textfelder eingeben, werden die anderen entsprechend aktualisiert. Hier ist die endgültige Version des Projekts auf GitHub .

    
Nate Chandler 02.11.2012, 06:10
quelle