Discussion:
Binding NSTextField to an array
Jeremy Hughes
2017-03-06 13:10:06 UTC
Permalink
Hi,

This seems like an elementary question.

I’d like to bind an NSTextField to an array of numerical values, so that the text field will either display a single value if the values are identical or will display a multiple values marker if the values are different.

Using Swift, I can bind a text field to a single value like this:

dynamic var value = NSNumber(value: 7)

But if I try to bind to an array of values like this:

dynamic var values = [NSNumber(value: 7), NSNumber(value: 3)]

I get the following error:

Cannot create number from object (
7,
3
) of class _TtGCs23_ContiguousArrayStorageCSo8NSNumber_

How do I bind an NSTextField to an array?

Jeremy


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive
Mike Abdullah
2017-03-06 13:16:37 UTC
Permalink
From what I understand of your example, you’re not “binding” anything in a Cocoa sense.

What you is an NSArrayController. Bind your text field to the array controller. Supply the array controller with content, and have it derive the selected value, be it single or multiple.
Post by Jeremy Hughes
Hi,
This seems like an elementary question.
I’d like to bind an NSTextField to an array of numerical values, so that the text field will either display a single value if the values are identical or will display a multiple values marker if the values are different.
dynamic var value = NSNumber(value: 7)
dynamic var values = [NSNumber(value: 7), NSNumber(value: 3)]
Cannot create number from object (
7,
3
) of class _TtGCs23_ContiguousArrayStorageCSo8NSNumber_
How do I bind an NSTextField to an array?
Jeremy
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/mabdullah%40karelia.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to
Jeremy Hughes
2017-03-06 13:32:06 UTC
Permalink
Post by Mike Abdullah
From what I understand of your example, you’re not “binding” anything in a Cocoa sense.
In the case of the single value, the text field is set up via the Bindings pane of Interface Builder so that “Value" says “Bind to File’s Owner” with a model key path of self.value. (And “value" is declared as dynamic so that Swift will take care of KVO.) This works fine. Is this not a Cocoa binding?

What I don’t understand is why this works for a single value but doesn’t work for an array of values, where I change the model key path to self.values.
Post by Mike Abdullah
What you is an NSArrayController. Bind your text field to the array controller. Supply the array controller with content, and have it derive the selected value, be it single or multiple.
OK. I now have an array controller that is bound to File’s Owner with a model key path of self.values, and I then bind the text field to the array controller with a Controller Key value of “selection” (although I’m not sure that’s right, because the array is not actually displayed anywhere for users to select items).

Now I get the following error:

Cannot create number from object <_NSControllerObjectProxy: 0x6000000070b0> of class _NSControllerObjectProxy

Jeremy


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This
Jonathan Mitchell
2017-03-06 14:30:29 UTC
Permalink
Sounds like NSValueTransformer is in fact what you need.
It can take in your NSArray ref and spit out a single NSString that the NSTextField binding can live with.

Defining NSValueTransformer subclass for every binding can be a pain.
So I use a block approach. This has the big advantage that you can capture other variables if required - like calling a weak self method:

BPBlockValueTransformer * fooTransformer = [BPBlockValueTransformer valueTransformerWithBlock:^id(NSArray *a) {
return [welf soSomethingWith:a];
}];
[self.fooView bind:NSValueBinding toObject:self withKeyPath:@“foo” options:@{NSValueTransformerBindingOption: fooTransformer}];


// subclass
@interface BPBlockValueTransformer : NSValueTransformer

+ (instancetype)valueTransformerWithBlock:(id(^)(id value))block;
@property (nonatomic,strong) id (^transform)(id value);

@end

@implementation BPBlockValueTransformer

+ (instancetype)valueTransformerWithBlock:(id(^)(id value))block
{
BPBlockValueTransformer *transformer = [[self alloc] init];
transformer.transform = block;

return transformer;
}

+ (Class)transformedValueClass
{
return [NSObject class];
}

- (id)transformedValue:(id)value
{
return self.transform(value);
}

@end
Post by Jeremy Hughes
Post by Mike Abdullah
From what I understand of your example, you’re not “binding” anything in a Cocoa sense.
In the case of the single value, the text field is set up via the Bindings pane of Interface Builder so that “Value" says “Bind to File’s Owner” with a model key path of self.value. (And “value" is declared as dynamic so that Swift will take care of KVO.) This works fine. Is this not a Cocoa binding?
What I don’t understand is why this works for a single value but doesn’t work for an array of values, where I change the model key path to self.values.
Post by Mike Abdullah
What you is an NSArrayController. Bind your text field to the array controller. Supply the array controller with content, and have it derive the selected value, be it single or multiple.
OK. I now have an array controller that is bound to File’s Owner with a model key path of self.values, and I then bind the text field to the array controller with a Controller Key value of “selection” (although I’m not sure that’s right, because the array is not actually displayed anywhere for users to select items).
Cannot create number from object <_NSControllerObjectProxy: 0x6000000070b0> of class _NSControllerObjectProxy
Jeremy
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/lists%40mugginsoft.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in
Jeremy Hughes
2017-03-06 16:44:22 UTC
Permalink
Post by Jonathan Mitchell
Sounds like NSValueTransformer is in fact what you need.
It can take in your NSArray ref and spit out a single NSString that the NSTextField binding can live with.
I think I must be missing something obvious.

The value of an NSTextField can be bound (in Interface Builder) to a property in File’s Owner or to an array controller that is bound to a property in File’s Owner. There is a checkbox in Interface Builder that says “Allows Editing Multiple Values Selection”.

This suggests to me that I can bind a text field to an array or (if that's not possible) to an array controller that is bound to the array. If there are multiple values, the controller should return NSMultipleValuesMarker, which according to Apple’s documentation "indicates that more than one object is selected in the controller and the values for the requested key aren’t the same.”

The documentation goes on to say "For example, if the value for selection.name returns an array containing three strings—”Tony”, “Tony”, “Tony”—the string “Tony” is returned instead of the NSMultipleValuesMarker.”

What I’m trying to do is to display a textfield (with a number formatter) which should display an integer value (such as “7”) if all the values in the array are the same, or a multiple-values indicator (like “-“) if the values are not the same.

I can get the binding to work if I bind the text field to a single value, but I can’t get it to work with an array of values or with an array controller that is bound to an array of values.

Maybe I should give up on using bindings to do this, but it would be good to understand what I’m doing wrong.

Jeremy


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive
Mike Abdullah
2017-03-06 17:12:42 UTC
Permalink
Yep, you’re very close. An array controller is required indeed, it’s the one responsible for vending out NSMultipleValuesMarker.

From your earlier message, you’re now seeing an exception or similar:
Cannot create number from object <_NSControllerObjectProxy: 0x6000000070b0> of class _NSControllerObjectProxy

This is because you’ve bound the field directly to the array controller’s .selection property. .selection returns a proxy object representing the selection which can then be queried for other properties. In your case you want to bind to “selection.self” instead I believe.

When binding your array controller to its content, there’s also an option to have it automatically select all (or something to that effect). You want to have this turned on too so that the array controller has a selection to generate values from.

Mike.
Post by Jeremy Hughes
Post by Jonathan Mitchell
Sounds like NSValueTransformer is in fact what you need.
It can take in your NSArray ref and spit out a single NSString that the NSTextField binding can live with.
I think I must be missing something obvious.
The value of an NSTextField can be bound (in Interface Builder) to a property in File’s Owner or to an array controller that is bound to a property in File’s Owner. There is a checkbox in Interface Builder that says “Allows Editing Multiple Values Selection”.
This suggests to me that I can bind a text field to an array or (if that's not possible) to an array controller that is bound to the array. If there are multiple values, the controller should return NSMultipleValuesMarker, which according to Apple’s documentation "indicates that more than one object is selected in the controller and the values for the requested key aren’t the same.”
The documentation goes on to say "For example, if the value for selection.name returns an array containing three strings—”Tony”, “Tony”, “Tony”—the string “Tony” is returned instead of the NSMultipleValuesMarker.”
What I’m trying to do is to display a textfield (with a number formatter) which should display an integer value (such as “7”) if all the values in the array are the same, or a multiple-values indicator (like “-“) if the values are not the same.
I can get the binding to work if I bind the text field to a single value, but I can’t get it to work with an array of values or with an array controller that is bound to an array of values.
Maybe I should give up on using bindings to do this, but it would be good to understand what I’m doing wrong.
Jeremy
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/mabdullah%40karelia.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml
Jeremy Hughes
2017-03-06 17:34:03 UTC
Permalink
Bingo and thanks!

I had already found the checkbox for “Selects All When Setting Content"

The missing piece of the jigsaw was using “selection" as the Controller Key and “self" as the Model Key Path.

It all works now.

Jeremy

--
Post by Mike Abdullah
Yep, you’re very close. An array controller is required indeed, it’s the one responsible for vending out NSMultipleValuesMarker.
Cannot create number from object <_NSControllerObjectProxy: 0x6000000070b0> of class _NSControllerObjectProxy
This is because you’ve bound the field directly to the array controller’s .selection property. .selection returns a proxy object representing the selection which can then be queried for other properties. In your case you want to bind to “selection.self” instead I believe.
When binding your array controller to its content, there’s also an option to have it automatically select all (or something to that effect). You want to have this turned on too so that the array controller has a selection to generate values from.
Mike.
Post by Jeremy Hughes
Post by Jonathan Mitchell
Sounds like NSValueTransformer is in fact what you need.
It can take in your NSArray ref and spit out a single NSString that the NSTextField binding can live with.
I think I must be missing something obvious.
The value of an NSTextField can be bound (in Interface Builder) to a property in File’s Owner or to an array controller that is bound to a property in File’s Owner. There is a checkbox in Interface Builder that says “Allows Editing Multiple Values Selection”.
This suggests to me that I can bind a text field to an array or (if that's not possible) to an array controller that is bound to the array. If there are multiple values, the controller should return NSMultipleValuesMarker, which according to Apple’s documentation "indicates that more than one object is selected in the controller and the values for the requested key aren’t the same.”
The documentation goes on to say "For example, if the value for selection.name returns an array containing three strings—”Tony”, “Tony”, “Tony”—the string “Tony” is returned instead of the NSMultipleValuesMarker.”
What I’m trying to do is to display a textfield (with a number formatter) which should display an integer value (such as “7”) if all the values in the array are the same, or a multiple-values indicator (like “-“) if the values are not the same.
I can get the binding to work if I bind the text field to a single value, but I can’t get it to work with an array of values or with an array controller that is bound to an array of values.
Maybe I should give up on using bindings to do this, but it would be good to understand what I’m doing wrong.
Jeremy
_______________________________________________
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
https://lists.apple.com/mailman/options/cocoa-dev/mabdullah%40karelia.com
_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkiv
Jeremy Hughes
2017-03-06 19:31:46 UTC
Permalink
Post by Jeremy Hughes
It all works now.
Actually, it works as far as the text field displays “Multiple” (placeholder) for multiple values, but it doesn’t work when I set a value in the text field. In that case I get:

Error setting value for key path selection.self of object <NSArrayController: 0x6080001c22b0>[object class: NSMutableDictionary, number of selected objects: 2] (from bound object <NSTextField: 0x6080001e2500>): [<__NSCFNumber 0x337> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key self.

The text field is bound to an array of NSNumbers via an array controller (using a Swift array that is marked as “dynamic”)

I can get around this by writing a “Number” wrapper:

class Number: NSObject
{
dynamic var value: NSNumber

init(_ value: Int) { self.value = NSNumber(value: value) }
}

dynamic var values: [Number]

and binding the text field to the “value” field of this wrapper (so the controller key is arrayController.selection and the model key path is “value").

But I wonder if it is possible to bind directly to an array of NSNumbers without using a wrapper.

I’ve tried doing that using an array of NSNumbers with the model key path set to “value” (i.e. the value of the NSNumber) but I get the following error:

[<__NSCFNumber 0x337> valueForUndefinedKey:]: this class is not key value coding-compliant for the key value.

So, unless there’s another key path I can use for NSNumber, I’m guessing it isn’t possible to do this.

Jeremy


_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-in.narkive.ne
Quincey Morris
2017-03-06 22:07:35 UTC
Permalink
Post by Jeremy Hughes
But I wonder if it is possible to bind directly to an array of NSNumbers without using a wrapper.
No, but the reason for that and for the trouble you’ve run into is a bit complicated.

First of all, you are actually talking about bind *through* an array of NSNumbers, overall, not to *it*. You can never bind through an array of anything. It’s a limitation of KVC, and nothing to do with NSArrayController in any direct sense.

Array controllers have two, asymmetrical, sides. On the content side, there’s a content collection of some sort, typically modeled as an array. This *can* be set up with a content binding, but it isn’t the only way — it can be a connection instead. The fact that you chose to use a content binding is irrelevant to to everything else that’s going on.

The objects vended by the array controller are a collection of values obtained from the content array, but filtered and sorted internally by the array controller. Of those objects, if you go through the “selection” or “selectedObjects” properties, you only “see” what is selected. When the client side binding is to a scalar control such as a text field, the client side objects aren’t functionally an array. Instead, you either have a single object (*the* selected object) which is bound as an individual, or a single marker object representing the multiple selection. There is magic (pseudo-magic) within the array controller that translates between the single object behavior of the control and the array of selected objects that it maintains internally.

All of this you’ve already discovered experimentally, but there’s nothing going on on the client side that functionally binds to or through an array. The array controller is doing something clever (pseudo-magical) instead. You are in fact binding only to single objects, and the array controller kinda turns this into binding to multiple single objects at the same time.

That’s why it breaks when you try to edit the values. When you bind a text field to a single object, the object must be mutable or you can’t change its value. Binding to “selection” or “selectedObjects” has similar semantics, and fails if the objects are not individually mutable. In your case, it’s your underlying array that’s mutable, not your values, and that’s not good enough.

OK, so what we’re really talking about is binding to immutable objects. I’m not sure that I’ve ever run into the case of using NSNumber objects, but I have run into the case of using NSString objects. That’s even weirder, because there are mutable strings, but they’re a different class — NSMutableString instead of NSString. You can editably bind to a single NSMutableString value, but it probably won’t work for very long because it’s also a NSString, and the mutable string will probably end up getting replaced by an immutable string pretty soon, and after that things will fall apart. With NSNumber things just fall apart immediately, which saved you some time of going further down the wrong path.

The clue to all this was the hack of using “self” as a model key. (You can also leave the model key blank to mean the same thing, although in some key fields IB might force you to enter something, hence the “self”.) It’s not that it’s wrong, and if your text field was not editable you would have already had a working solution, but that it signals that you’re not properly working with *properties of objects*, which is the essence of both KVC and bindings. You were in effect trying to work with objects without properties, your NSNumber objects.

That’s why your Number wrapper approach was both the right thing to do and worked. Now, you had objects with a “value” property that you could bind to. I realize it feels wasteful to have custom objects with no other properties, but I’m guessing, if you think this through in larger terms, that you may find that this value is really a property of a more complicated model object, and that you should be setting your array controller’s content to a collection of those more complicated model objects. (Perhaps.)

The takeaway here, the moral of the story, is that taking shortcuts with KVC and bindings — using standard objects such as arrays, dictionaries, numbers and strings — almost always gets you into trouble as soon as you exceed the simplest of app requirements. You really must think in terms of properties, not values. Once you do that, everything falls into place and it becomes clear what to do.

_______________________________________________

Cocoa-dev mailing list (Cocoa-***@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/cocoa-dev/gegs%40ml-in.narkive.net

This email sent to ***@ml-i

Loading...