NSTextField 上的键值观察
In your -awakeFromNib
方法的实现,你已经写了
[self.fieldA addObserver:self
forKeyPath:@"numA"
options:0
context:&MainClassKVOContext];
这并没有达到您希望的效果:self.fieldA
is not 符合键值编码对于钥匙numA
: 如果你尝试发送-valueForKey:
or -setValue:forKey:
用钥匙@"numA"
to self.fieldA
,您将得到以下异常:
[ valueForUndefinedKey:]:此类对于键 numA 不符合键值编码。
and
[ setValue:forUndefinedKey:]:此类对于键 numA 不符合键值编码。
因此, the NSTextField
实例不是核心价值观察合规的 for @"numA"
,或者:某些密钥符合 KVO 的第一个要求是该密钥符合 KVC。
然而,它符合 KVO 标准,除其他外,stringValue
。这可以让你做我之前描述的.
Note:这一切都不会因您在 Interface Builder 中设置绑定的方式而改变。稍后会详细介绍。
NSTextField 的 stringValue 上键值观察的问题
观察一个NSTextField
的价值为@"stringValue"
工作时-setStringValue:
被呼叫NSTextField
。这是 KVO 内部结构的结果。
KVO 内部结构简述
当你第一次开始观察一个对象的键值对时,该对象的类发生了变化——它的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
to NSKVONotifying_Object
.
如果我们打电话的话-addObserver:forKeyPath:options:context:
在一个实例上Object
与 键路径@"property"
-- 实例的键Object
是否符合 KVC 标准——下次我们致电时-setProperty:
在我们的例子中Object
(事实上,现在有一个实例NSKVONotifying_Object
),以下消息将被发送到对象
-
-willChangeValueForKey:
通过@"property"
-
-setProperty:
通过@"property"
-
-didChangeValueForKey:
通过@"property"
破坏这些方法中的任何一个都表明它们是从未记录的函数中调用的_NSSetObjectValueAndNotify
.
所有这一切的相关性在于该方法-observeValueForKeyPath:ofObject:change:context:
调用我们添加到实例中的观察者Object
对于关键路径@"property"
from -didChangeValueForKey:
。这是堆栈跟踪的顶部:
-[Observer observeValueForKeyPath:ofObject:change:context:]
NSKeyValueNotifyObserver ()
NSKeyValueDidChange ()
-[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
这有什么关系NSTextField
and @"stringValue"
?
In your 之前的设置,您正在将观察者添加到文本字段中-awakeFromNib
。这意味着您的文本字段已经是一个实例NSKVONotifying_NSTextField
.
然后,您按下一个或另一个按钮,该按钮又会调用-setStringValue
在您的文本字段上。你能够观察到这种变化是因为——作为一个例子NSKVONotifying_NSTextField
--您的文本字段,收到后setStringValue:value
实际收到
willChangeValueForKey:@"stringValue"
setStringValue:value
didChangeValueForKey:@"stringValue"
如上所述,从内部didChangeValueForKey:@"stringValue"
,所有正在观察文本字段值的对象@"stringValue"
被通知该键的值在他们自己的实现中已更改-observeValueForKeyPath:ofObject:change:context:
。特别是,对于您添加为文本字段观察者的对象来说更是如此。-awakeFromNib
.
总之,您能够观察到文本字段值的变化@"stringValue"
因为您将自己添加为该键的文本字段的观察者并且because -setStringValue
在文本字段上被调用.
所以有什么问题?
到目前为止,在讨论“NSTextFields 上键值观察的麻烦”的幌子下,我们实际上只理解了开头那句
观察一个NSTextField
的价值为@"stringValue"
工作时-setStringValue:
被呼叫NSTextField
.
听起来很棒!所以有什么问题?
问题是-setStringValue:
当用户在文本字段中输入时,或者甚至在用户结束编辑(例如,通过跳出文本字段)后,不会在文本字段上调用。 (此外,-willChangeValueForKey:
and -didChangeValueForKey:
不被手动调用。如果是的话,我们的 KVO 就会起作用;但事实并非如此。)这意味着当我们的 KVO 开启时@"stringValue"
工作时-setStringValue:
在文本字段上调用,它确实NOT当用户自己输入文本时工作。
TL;DR:KVO 上@"stringValue"
of an NSTextField
还不够好,因为它不适用于用户输入。
将 NSTextField 的值绑定到字符串
让我们尝试使用绑定。
初始设置
使用单独的窗口控制器创建一个示例项目(我使用了创意名称WindowController
)与 XIB 一起完成。 (这是我在 GitHub 上启动的项目。) In WindowController.m
添加了一个属性stringA
在类扩展中:
@interface WindowController ()
@property (nonatomic) NSString *stringA;
@end
在 Interface Builder 中,创建一个文本字段并打开 Bindings Inspector:
在“值”标题下,展开“值”项:
“绑定到”复选框旁边的弹出按钮目前已选择“共享用户默认控制器”。我们想要将文本字段的值绑定到我们的WindowController
例如,因此选择“文件所有者”。发生这种情况时,“控制器密钥”字段将被清空,“模型密钥路径”字段将更改为“self”。
我们想要将此文本字段的值绑定到我们的WindowController
实例的属性stringA
因此将“模型关键路径”更改为self.stringA
:
至此,我们就完成了。 (GitHub 上迄今为止的进展。)我们已经成功地将文本字段的值绑定到我们的WindowController
的财产stringA
.
测试一下
如果我们设置stringA
到 -init 中的某个值,该值将在窗口加载时显示在文本字段中:
- (id)init
{
self = [super initWithWindowNibName:@"WindowController"];
if (self) {
self.stringA = @"hello world";
}
return self;
}
我们已经在另一个方向上设置了绑定;结束在文本字段中的编辑后,我们的窗口控制器的属性stringA
已设置。我们可以通过重写它的 setter 来检查这一点:
- (void)setStringA:(NSString *)stringA
{
NSLog(@"%s: stringA: <<%@>> => <<%@>>", __PRETTY_FUNCTION__, _stringA, stringA);
_stringA = stringA;
}
回复 朦胧,再试一次
在文本字段中输入一些文本并按 Tab 键后,我们将看到打印出来的内容
-[WindowController setStringA:]: stringA: <<(null)>> => <<some text>>
这看起来很棒。为什么我们一直不讨论这个???这里有一个小问题:令人讨厌的按下标签事物。将文本字段的值绑定到字符串不会设置字符串值,直到文本字段中的编辑结束为止。
新希望
不过,还是有希望的!这Cocoa 绑定文档NSTextField指出一个绑定选项可用于NSTextField
is NSContinuouslyUpdatesValueBindingOption
。你瞧,在绑定检查器中,有一个与 NSTextField 值的选项对应的复选框。继续并选中该框。
完成此更改后,当我们输入内容时,窗口控制器的更新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>>
最后,我们不断更新文本字段中窗口控制器的字符串。剩下的就很容易了。作为概念的快速证明,向窗口中添加更多文本字段,将它们绑定到 stringA 并将它们设置为持续更新。此时您已同步了三个NSTextField
s! 这是具有三个同步文本字段的项目。
剩下的路
您想要设置三个文本字段来显示彼此有某种关系的数字。由于我们现在处理的是数字,因此我们将删除该属性stringA
from WindowController
并将其替换为numberA
, numberB
and numberC
:
@interface WindowController ()
@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end
接下来,我们将第一个文本字段绑定到文件所有者的 numberA,第二个文本字段绑定到 numberB,依此类推。最后,我们只需要添加一个属性,即以这些不同方式表示的数量。我们称这个值为quantity
.
@interface WindowController ()
@property (nonatomic) NSNumber *quantity;
@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end
我们需要恒定的转换因子来从单位转换quantity
到单位numberA
等等,所以添加
static float convertToA = 1000.0f;
static float convertToB = 573.0f;
static float convertToC = 720.0f;
(当然,使用与您的情况相关的数字。)有了这些,我们就可以为每个数字实现访问器:
- (NSNumber *)numberA
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToA];
}
- (void)setNumberA:(NSNumber *)numberA
{
self.quantity = [NSNumber numberWithFloat:numberA.floatValue * 1.0f/convertToA];
}
- (NSNumber *)numberB
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToB];
}
- (void)setNumberB:(NSNumber *)numberB
{
self.quantity = [NSNumber numberWithFloat:numberB.floatValue * 1.0f/convertToB];
}
- (NSNumber *)numberC
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToC];
}
- (void)setNumberC:(NSNumber *)numberC
{
self.quantity = [NSNumber numberWithFloat:numberC.floatValue * 1.0f/convertToC];
}
所有不同的数字访问器现在都只是访问的间接机制quantity
,并且非常适合装订。只有一件额外的事情需要做:我们需要确保观察者在任何时候重新轮询所有数字quantity
被改变:
+ (NSSet *)keyPathsForValuesAffectingNumberA
{
return [NSSet setWithObject:@"quantity"];
}
+ (NSSet *)keyPathsForValuesAffectingNumberB
{
return [NSSet setWithObject:@"quantity"];
}
+ (NSSet *)keyPathsForValuesAffectingNumberC
{
return [NSSet setWithObject:@"quantity"];
}
现在,每当您在其中一个文本字段中输入内容时,其他文本字段都会相应更新。这是 GitHub 上该项目的最终版本.