对绑定在一起的 NSTextField 使用 KVO

2023-11-22

我在使用 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

And here is the binding for one of the text fields: enter image description here


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),以下消息将被发送到对象

  1. -willChangeValueForKey:通过@"property"
  2. -setProperty:通过@"property"
  3. -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实际收到

  1. willChangeValueForKey:@"stringValue"
  2. setStringValue:value
  3. 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:

Bindings Inspector

在“值”标题下,展开“值”项:

NSControl Value Binding

“绑定到”复选框旁边的弹出按钮目前已选择“共享用户默认控制器”。我们想要将文本字段的值绑定到我们的WindowController例如,因此选择“文件所有者”。发生这种情况时,“控制器密钥”字段将被清空,“模型密钥路径”字段将更改为“self”。

Binding NSControl Value to File's Owner

我们想要将此文本字段的值绑定到我们的WindowController实例的属性stringA因此将“模型关键路径”更改为self.stringA:

Binding NSTextField's Value to File's Owner's property stringA

至此,我们就完成了。 (GitHub 上迄今为止的进展。)我们已经成功地将文本字段的值绑定到我们的WindowController的财产stringA.

测试一下

如果我们设置stringA到 -init 中的某个值,该值将在窗口加载时显示在文本字段中:

- (id)init
{
    self = [super initWithWindowNibName:@"WindowController"];
    if (self) {
        self.stringA = @"hello world";
    }
    return self;
}

Showing 'hello world' in Text Field

我们已经在另一个方向上设置了绑定;结束在文本字段中的编辑后,我们的窗口控制器的属性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 值的选项对应的复选框。继续并选中该框。

NSTextField's value binding continuously updates stringA

完成此更改后,当我们输入内容时,窗口控制器的更新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 并将它们设置为持续更新。此时您已同步了三个NSTextFields! 这是具有三个同步文本字段的项目。

剩下的路

您想要设置三个文本字段来显示彼此有某种关系的数字。由于我们现在处理的是数字,因此我们将删除该属性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 上该项目的最终版本.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对绑定在一起的 NSTextField 使用 KVO 的相关文章

  • ARC 不允许将 Objective-C 指针隐式转换为“void *”

    这意味着什么 我有什么选择 ARC 不允许将 Objective C 指针隐式转换为 void 我正在将 Xcode3 项目移植到 iOS5 它使用 AudioSessionInitialize 如下 AudioSessionInitial
  • 沙盒尝试恢复消耗性 IAP

    我一直在尝试在 iOS 上测试一些消耗性 IAP 但遇到了一个奇怪的错误 弹出一条警报 其中包含以下文本 此应用内购买已被购买 它将恢复为 自由的 环境 沙盒 我已经检查过 并且确定我的 IAP 可以在 iTunesConnect 中使用
  • ios 在后台处理推送通知

    我想保存应用程序处于后台状态时到达的推送通知 我知道关于 void application UIApplication application didReceiveRemoteNotification NSDictionary userIn
  • 获取 Mojave 上的鼠标坐标

    我有一个非常基本的小命令行应用程序 可以在下次单击鼠标时获取鼠标坐标 import
  • ios 导航 堆栈操作

    我在尝试从 iOS 应用程序操作导航堆栈时遇到问题 或者至少是由于这种操纵而产生的行为 我的情况 我有 3 个 ViewController 控制器a显示多个级别 控制器 b 是游戏视图 控制器 c 是某种分数 显然 我将在控制器 a 中选
  • Objective-c 中的块递归

    当执行涉及 Objective C 块的递归时 我在 iOS 应用程序中收到 EXC BAD ACCESS 信号 这是简化的代码 void problematicMethod FriendInfo friendInfo onComplete
  • 如何解决 CoreData mogenerator 未找到问题

    我收到如下所示的错误 我不知道我错过了什么 我该如何解决这个问题 如下图所示 Users nischalhada Documents XcodePro mnepalnews revisited 2 0 CoreData mogenerato
  • 为沙盒 Cocoa 应用程序创建临时文件

    我的应用程序是沙箱化的 根据最新的应用程序商店指南 我想创建一些临时文件 我可以这样做吗 如果 是 我可以在哪里这样做 有没有预先指定的路径 还有访问该路径的命令 您应该使用NSTemporaryDirectory 函数 它将查找并返回适合
  • 在 swrevealcontroller 之前实现登录屏幕

    我刚刚开始学习 IOS 开发 我已经按照给定的在线教程成功实现了 SWRevealViewController 一切都按预期工作 然后 我决定添加一个登录屏幕 这将是应用程序运行时用户看到的第一个页面 我采取的步骤如下 将 UIViewCo
  • 如何使用MKMapView完成加载委托,可能的“完成显示”委托?

    当用户在选择注释后点击 保存 时 我尝试保存地图视图的缩略图 当用户尚未放大该注释时会出现问题 因此尚未加载关闭缩放级别 这就是用户点击保存后我正在做的事情 将布尔值 saving 设置为 true 居中并放大注释 无动画 当调用mapVi
  • 如何在 Xcode 4 中在 .h 和 .m 之间切换

    刚刚安装的 Xcode 4 到目前为止一切顺利 除了 Apple 更改了所有键盘快捷键 有人知道如何在 h 和 m 之间切换吗 苹果改变了各种快捷键 但要在标头和实现之间切换 新的快捷键是 You can change it back to
  • ViewWillAppear 没有被 UISplitViewController 调用

    背景和目标 我有一个基于 UISplitViewController 的 iPad 应用程序 到目前为止它支持 4 个方向 但现在我想将其锁定为仅横向 我变了shouldAutorotateToInterfaceOrientation左视图
  • 多次添加同一个子视图来查看

    我不知道这是否可行 但我想做的是将子视图多次添加到视图中 我尝试过这样的事情 self view addSubview newView newView center CGPointMake 160 100 self view addSubv
  • GMSMapView 中的倒多边形

    我必须在我的 iPhone 项目中使用 Google 地图 并且我正在使用 GMSPolygon 来绘制多边形 但是如何填充地图上除多边形内部之外的所有位置 就像下图一样 谢谢 我玩过你的问题 主要思想是用多边形填充整个地球 然后为您的特定
  • 使用超类初始化器初始化类

    我有两个类 一个是另一个的子类 比如说Animal and Dog 超类有一些初始化器 比如initAnimal 子类有一些初始化器 比如initDog 问题是 从编译器的角度来看 做类似的事情是完全合法的Dog adog Dog allo
  • PrepareForSegue之谜

    我在两个不同的 VC 中有一个prepareForSegue 方法 一个使用一个if声明 而另一个旨在使用switch 除了名称之外 代码几乎相同 这个效果很好 void prepareForSegue UIStoryboardSegue
  • Objective C UIImagePNGRepresentation内存问题(使用ARC)

    我有一个基于 ARC 的应用程序 它从 Web 服务加载大约 2 000 个相当大 1 4MB 的 Base64 编码图像 它将 Base64 解码后的字符串转换为 png图像文件并将其保存到磁盘 这一切都是在一个循环中完成的 我不应该有任
  • 我可以知道 requireGestureRecognizerToFail 到底会做什么吗?

    谁能告诉我下面的代码行到底会做什么 我已经提到过Apples https developer apple com library ios documentation uikit reference UIGestureRecognizer C
  • 如何更改已上传的 Firebase 存储图像文件名?

    我需要更改已上传到 firebase 存储中的文件名 因为 在 firebase 存储中上传图像后 我将 url 保存在 firebase 数据库中的特定子 文件夹 下 但是 当我将图像移动到另一个子 文件夹 时 我需要根据新名称更改存储中
  • 将 CALayer 旋转 90 度?

    如何旋转CALayer90度 我需要旋转所有内容 包括子图层和坐标系 Obj C theLayer transform CATransform3DMakeRotation 90 0 180 0 M PI 0 0 0 0 1 0 Swift

随机推荐