我无法让KVO处理在Cocoa应用程序中绑定在一起的文本字段。我在使用按钮在NSTextFields中设置字符串但是它不能使用绑定时,我已经得到了这个功能。与往常一样,Stack Overflow的任何帮助都将不胜感激。
我的代码的目的是:
将多个文本字段绑定在一起
在一个字段中输入数字时,其他字段会自动更新
观察文本字段中的更改
这是我的MainClass代码,它是一个NSObject子类:
#import "MainClass.h"
@interface MainClass ()
@property (weak) IBOutlet NSTextField *fieldA;
@property (weak) IBOutlet NSTextField *fieldB;
@property (weak) IBOutlet NSTextField *fieldC;
@property double numA, numB, numC;
@end
@implementation MainClass
static int MainClassKVOContext = 0;
- (void)awakeFromNib {
[self.fieldA addObserver:self forKeyPath:@"numA" options:0 context:&MainClassKVOContext];
[self.fieldB addObserver:self forKeyPath:@"numB" options:0 context:&MainClassKVOContext];
[self.fieldC addObserver:self forKeyPath:@"numC" options:0 context:&MainClassKVOContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != &MainClassKVOContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if (object == self.fieldA) {
if ([keyPath isEqualToString:@"numA"]) {
NSLog(@"fieldA length = %ld", [_fieldA.stringValue length]);
}
}
if (object == self.fieldB) {
if ([keyPath isEqualToString:@"numB"]) {
NSLog(@"fieldB length = %ld", [_fieldB.stringValue length]);
}
}
if (object == self.fieldC) {
if ([keyPath isEqualToString:@"numC"]) {
NSLog(@"fieldC length = %ld", [_fieldC.stringValue length]);
}
}
}
+ (NSSet *)keyPathsForValuesAffectingNumB {
return [NSSet setWithObject:@"numA"];
}
+ (NSSet *)keyPathsForValuesAffectingNumC {
return [NSSet setWithObject:@"numA"];
}
- (void)setNumB:(double)theNumB {
[self setNumA:theNumB * 1000];
}
- (double)numB {
return [self numA] / 1000;
}
- (void)setNumC:(double)theNumC {
[self setNumA:theNumC * 1000000];
}
- (double)numC {
return [self numA] / 1000000;
}
- (void)setNilValueForKey:(NSString*)key {
if ([key isEqualToString:@"numA"]) return [self setNumA: 0];
if ([key isEqualToString:@"numB"]) return [self setNumB: 0];
if ([key isEqualToString:@"numC"]) return [self setNumC: 0];
[super setNilValueForKey:key];
}
@end
以下是其中一个文本字段的绑定:
答案 0 :(得分:35)
在-awakeFromNib
方法的实施中,您已撰写
[self.fieldA addObserver:self
forKeyPath:@"numA"
options:0
context:&MainClassKVOContext];
这并不是您所希望的:self.fieldA
不是 key-value coding compliant 代码为numA
:如果您尝试使用密钥-valueForKey:
向-setValue:forKey:
发送@"numA"
或self.fieldA
,您将获得以下例外情况:
[valueForUndefinedKey:]:此类与密钥numA不符合密钥值。
和
[setValue:forUndefinedKey:]:此类与密钥numA不符合密钥值编码。
As a result,NSTextField
个实例不是键值观察兼容 @"numA"
:第一个要求是符合KVO标准的某些密钥与该密钥的KVC兼容。
stringValue
。这样您就可以what I described earlier。
注意:这些都不会因您在Interface Builder中设置绑定的方式而改变。稍后会详细介绍。
在NSTextField
上调用@"stringValue"
时,-setStringValue:
的{{1}}值会有效。这是KVO内部的结果。
当您开始观察第一次观察对象的键值时,对象的类被更改 - 其NSTextField
指针被更改。你可以通过覆盖isa
-addObserver:forKeyPath:options:context:
通常,班级名称从- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa));
[super addObserver:observer
forKeyPath:keyPath
options:options
context:context];
NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa));
}
更改为Object
。
如果我们使用密钥路径NSKVONotifying_Object
在-addObserver:forKeyPath:options:context:
的实例上调用了Object
- @"property"
的实例符合KVC标准的密钥 - 接下来我们在Object
的实例上调用-setProperty:
(事实上,现在是Object
的实例),以下消息将被发送到对象
NSKVONotifying_Object
传递-willChangeValueForKey:
@"property"
传递-setProperty:
@"property"
传递-didChangeValueForKey:
在这些方法中的任何一个中断都表明它们是从未记录的函数@"property"
中调用的。
所有这一切的相关性是,方法_NSSetObjectValueAndNotify
是针对我们添加到-observeValueForKeyPath:ofObject:change:context:
实例的观察者调用的,用于关键路径Object
来自 @"property"
。这是堆栈跟踪的顶部:
-didChangeValueForKey:
-[Observer observeValueForKeyPath:ofObject:change:context:]
NSKeyValueNotifyObserver ()
NSKeyValueDidChange ()
-[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
和NSTextField
在previous setup中,您在@"stringValue"
上的文字字段中添加了观察者。这意味着您的文本字段已经是-awakeFromNib
的实例。
然后,您可以按一个或另一个按钮,而该按钮又会在您的文本字段上调用NSKVONotifying_NSTextField
。您能够观察到此更改,因为 - 作为-setStringValue
的实例 - 您的文本字段,在收到实际收到的NSKVONotifying_NSTextField
时
setStringValue:value
willChangeValueForKey:@"stringValue"
setStringValue:value
如上所述,在didChangeValueForKey:@"stringValue"
内,所有观察didChangeValueForKey:@"stringValue"
文本字段值的对象都会收到通知,表示此密钥的值已在其自己的@"stringValue"
实现中发生变化{1}}。特别是,您在-observeValueForKeyPath:ofObject:change:context:
中作为文本字段的观察者添加的对象也是如此。
总之,您可以观察-awakeFromNib
的文本字段值的变化,因为您将自己添加为该键的文本字段的观察者,并且因为{{1在文本字段上调用。
到目前为止,在讨论" NSTextFields上的键值观察问题"我们只是真正理解了开场句
在
@"stringValue"
上调用-setStringValue
后,观察NSTextField
的{{1}}值是有效的。
这听起来很棒!那么问题是什么?
问题是@"stringValue"
在文本字段中没有被调用,因为用户正在键入它或者甚至在用户结束编辑之后(例如,通过跳出文本字段)。 (此外,-setStringValue:
和NSTextField
不是手动调用的。如果是,我们的KVO会起作用;但它不会。)这意味着我们在-setStringValue:
上的KVO工作了当在文本字段上调用-willChangeValueForKey:
时,当用户自己输入文本时, NOT 工作。
TL; DR :-didChangeValueForKey:
的{{1}}上的KVO不够好,因为它不适合用户输入。
让我们尝试使用绑定。
创建一个示例项目,其中包含一个单独的窗口控制器(我已使用广告素材名称@"stringValue"
)与XIB一起完成。 (Here's the project I'm starting from on GitHub.)在-setStringValue:
中,在类扩展中添加了属性@"stringValue"
:
NSTextField
在Interface Builder中,创建一个文本字段并打开Bindings Inspector:
在"价值"标题,扩展"价值"项:
" Bind to"旁边的弹出按钮;复选框目前有#34;共享用户默认值控制器"选择。我们希望将文本字段的值绑定到我们的WindowController
实例。所以选择"文件的所有者"代替。当发生这种情况时,"控制器键"字段将被清空并且"模型关键路径"字段将更改为" self"。
我们希望将此文本字段的值绑定到我们的WindowController.m
实例的属性stringA
,因此请更改"模型关键路径"到@interface WindowController ()
@property (nonatomic) NSString *stringA;
@end
:
此时,我们已经完成了。 (Progress so far on GitHub.)我们已成功将文本字段的值绑定到我们的WindowController
属性WindowController
。
如果我们将stringA
设置为-init中的某个值,那么当窗口加载时该值将显示在文本字段中:
self.stringA
而且我们已经在另一个方向设置了绑定;在文本字段中结束编辑后,我们的窗口控制器的属性WindowController
被设置。我们可以通过覆盖它的setter来检查这个:
stringA
在文本字段中输入一些文字并按Tab键后,我们会看到打印出来的
stringA
这看起来很棒。为什么我们一直在讨论这个问题?这里有点麻烦:讨厌的紧迫的选项卡。将文本字段的值绑定到字符串不会设置字符串值,直到编辑在文本字段中结束。
然而,仍有希望! Cocoa Binding Documentation for NSTextField
表示- (id)init
{
self = [super initWithWindowNibName:@"WindowController"];
if (self) {
self.stringA = @"hello world";
}
return self;
}
可用的一个绑定选项为stringA
。并且看,在Bindings Inspector中有一个与此选项对应的复选框,用于NSTextField的值。来吧,检查那个方框。
有了这个改变,当我们输入内容时,窗口控制器的- (void)setStringA:(NSString *)stringA
{
NSLog(@"%s: stringA: <<%@>> => <<%@>>", __PRETTY_FUNCTION__, _stringA, stringA);
_stringA = stringA;
}
属性的更新将被持续注销:
-[WindowController setStringA:]: stringA: <<(null)>> => <<some text>>
最后,我们不断从文本字段更新窗口控制器的字符串。其余的很容易。作为概念的快速证明,在窗口中添加几个文本字段,将它们绑定到stringA并将它们设置为连续更新。此时您有三个同步NSTextField
s! Here's the project with three synchronized text fields.
您想要设置三个显示彼此之间存在某种关系的数字的文本字段。由于我们现在正在处理数字,因此我们会从NSContinuouslyUpdatesValueBindingOption
中移除属性stringA
,并将其替换为-[WindowController setStringA:]: stringA: <<(null)>> => <<t>>
-[WindowController setStringA:]: stringA: <<t>> => <<th>>
-[WindowController setStringA:]: stringA: <<th>> => <<thi>>
-[WindowController setStringA:]: stringA: <<thi>> => <<thin>>
-[WindowController setStringA:]: stringA: <<thin>> => <<thing>>
-[WindowController setStringA:]: stringA: <<thing>> => <<things>>
-[WindowController setStringA:]: stringA: <<things>> => <<things >>
-[WindowController setStringA:]: stringA: <<things >> => <<things i>>
-[WindowController setStringA:]: stringA: <<things i>> => <<things in>>
,NSTextField
和{{1} }:
stringA
接下来,我们将第一个文本字段绑定到File&#39的所有者上的numberA,将第二个文本字段绑定到numberB,依此类推。最后,我们只需要添加一个属性,该属性是以这些不同方式表示的数量。我们称之为WindowController
。
numberA
我们需要使用常量转换因子从numberB
的单位转换为numberC
的单位,依此类推,所以添加
@interface WindowController ()
@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end
(当然,请使用与您的情况相关的数字。)有了这么多,我们可以为每个数字实现访问器:
quantity
所有不同数量的访问者现在只是访问@interface WindowController ()
@property (nonatomic) NSNumber *quantity;
@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end
的间接机制,非常适合绑定。还有一件事还有待完成:我们需要确保观察员在quantity
发生变化时重新收集所有数字:
numberA
现在,无论何时键入其中一个文本字段,其他字段都会相应更新。 Here's the final version of the project on GitHub