Change the null placeholder in a Cocoa binding? - cocoa

Is there a way to change (for the purpose of localization) the null placeholder in a binding in Cocoa?
The bindings are set up in Interface Builder for a popup button. The two-way nature of the bindings as set up in IB is needed, so doing it programmatically is not really appealing.
I am aware that the standard way of handling localizations of a nib file is by making one for each language, but since this is the only difference in the whole nib file between the language versions, it seems a bit excessive for a single string.
If there is a way to modify a binding created in IB, I was thinking about doing it in the file's owner's awakeFromNib method.

In the controller object to which you bind, such as your NSDocument class, override -bind:toObject:withKeyPath:options:. This needs to be the target of that method invocation – the object you select under Bind to: in the nib.
If you bind to an NSObjectController or NSArrayController, you'll need a subclass.
That method should rewrite the options dictionary and invoke super, replacing the value for NSNullPlaceholderBindingOption with your localized string.
I would omit the null placeholder from the nib and that key value in code, though you could of course take the passed-in value for that key and translate it, instead.

The other answer no longer seems to work so I've come up with a slightly different solution which modifies an existing binding to use the given null placeholder string:
I have this method in my view controller:
- (void)rebind:(NSString *)binding of:(id)object withNullPlaceholder:(NSString *)nullPlaceholder {
// Possibly a bad idea, but Xcode doesn't localize the null placeholder so we have do it manually.
NSDictionary *bindingInfo = [object infoForBinding:binding];
id bindObject = bindingInfo[NSObservedObjectKey];
NSString *keyPath = bindingInfo[NSObservedKeyPathKey];
NSMutableDictionary *options = [bindingInfo[NSOptionsKey] mutableCopy];
options[NSNullPlaceholderBindingOption] = nullPlaceholder;
[object unbind:binding];
[object bind:binding toObject:bindObject withKeyPath:keyPath options:options];
}
I call this in awakeFromNib for all the bindings that need it and pass in a localized string:
- (void)awakeFromNib {
// Hacky hack hack: Xcode is stupid and doesn't localize the null placeholders so we have to do it.
[self rebind:#"contentValues" of:self.fooPopup withNullPlaceholder:NSLocalizedString(#"No foos available", #"foo popup null placeholder")];
[self rebind:#"contentValues" of:self.barPopup withNullPlaceholder:NSLocalizedString(#"No bars available", #"bar popup null placeholder")];
}
The localized strings are then localized normally as part of the Localizable.strings file.

I was able to change the null placeholder string (i.e. "No Value") in a NSPopUpButton that uses bindings.
Specifically, I wanted to have an popup button menu item that had a title other than "No Value" with a represented object of nil. An empty NSString or nil should be saved in the user defaults when the null placeholder menu item is selected.
NSPopUpButton Bindings:
Content is bound to an NSArrayController.arrangedObjects
Content Objects is bound NSArrayController.arrangedObjects.exampleContentObject (NSString), this is the object that the menu item represents and is the Selected Object,
Content Values is bound to NSArrayController.arrangedObjects.exampleContentValue (NSString), this is the title shown in the popup button's menu item.
Selected Object of the popup button is bound to NSSharedUserDefaultsController.values.ExampleUserDefaultsKey which is the same object type as the Content Objects and Selected Object (NSString). This object should match the object type of the NSUserDefault's key specified in the binding. When an item from the popup button is selected, it will save the Selected Object to the user defaults.
To change the null placeholder string from "No Value" to something else, subclass NSPopUpButton and override -[NSPopUpButton bind:toObject:withKeyPath:options:].
#interface CustomPopUpButton : NSPopUpButton
#end
#implementation CustomPopUpButton
- (void)bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary<NSString *,id> *)options {
NSMutableDictionary *mutableOptions = options ? [options mutableCopy] : [NSMutableDictionary dictionaryWithCapacity:1];
mutableOptions[NSInsertsNullPlaceholderBindingOption] = #YES;
mutableOptions[NSNullPlaceholderBindingOption] = #"Custom Null Placeholder Text";
[super bind:binding toObject:observable withKeyPath:keyPath options:[mutableOptions copy]];
}
#end
Finally, select the NSPopUpButton in Interface Builder and under Custom Class in the Xcode Identity Inspector to your the class to the NSPopUpButton subclass.

Related

View-based NSTableView + NSButton

I've got a view-based NSTableView, using Cocoa Bindings to change the values of some labels and images in the cell. It all works great. However, I want to add a button to the cell. I've got the button working, but its action method only has the button as sender, which means I have no idea of the content of the cell that the button is in. Somehow I need to store some extra data on the button - at the very least the row index that the button is in. I subclassed NSButton and used my subclass in the cell, but Interface Builder doesn't know about the extra property so I can't bind to it. If I wanted to bind it in code, I don't know the name of the object or keypath that would be passed to it.
How can I get this to work?
You can use rowForView in your action method to get the row value
- (IBAction)doSomething:(id)sender
{
NSInteger row = [_myTableView rowForView:sender];
}
You can use the Identity field in Interface Builder to associate a table cell view from the nib with an instance in your code:
Additionally you have to implement - tableView:viewForTableColumn:row: in your table view's delegate. (Don't forget to connect the delegate in IB)
- (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:
(NSTableColumn*)tableColumn row:(NSInteger)row
{
SSWButtonTableCellView *result = [tableView makeViewWithIdentifier:#"ButtonView" owner:self];
result.button.title = [self.names objectAtIndex:row][#"name"];
result.representedObject = [self.names objectAtIndex:row];
return result;
}
I added representedObject property in my NSTableCellView subclass, which I set in the above table view delegate method.
Your custom table cell view can later use that object in it's action. e.g.:
- (IBAction)doSomething:(id)sender
{
NSLog(#"Represented Object:%#", self.representedObject);
}

Cocoa notification, how to observe for event?

I have a TextField in my Cocoa application. This TextField will be sometimes filled and sometimes empty.
I want it so that when the field is empty a button is disabled. Now I check the field whenever I do some action with Core Data from where the TextField gets its value.
I'd like for it to always be checked for this.
If the user edits the text field and brings up the field editor then you can use the following.
You can implemented the NSTextFieldDelegate protocol (which is really just the NSControlTextEditing protocol). For example, in a controller or a delegate object implement the control:textShouldEndEditing: method and use this to check the values of the new string. If the string is empty then disable the button.
// In a control object which has a reference to the NSTextField and the NSButton
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
if ([fieldEditor.string isEqualToString:#""])
{
[self.button setEnabled:NO];
return YES;
}
[self.button setEnabled:YES];
return YES;
}
Update: Sorry missed that bit about CodeData. In this case you should follow the instructions here, Disable/Enable NSButton if NSTextfield is empty or not,
The other way would be to give the controller a property exposing the
string value, bind the text field's value binding to this stringValue
property, and bind the button's enabled binding to the controller's
stringValue.length.

Disable/Enable NSButton if NSTextfield is empty or not

I´m newbie with cocoa. I have a button and a textField in my app. I want the button disabled when the textfield is empty and enabled when the user type something.
Any point to start? Any "magic" binding in Interface Builder?
Thanks
[EDITED]
I´ve tried to set the appDelegate as the NSTextfield´s delegate and added this method (myTextfield and myButton are IBOutlets):
- (void)textDidChange:(NSNotification *)aNotification
{
if ([[myTextField stringValue]length]>0) {
[myButton setEnabled: YES];
}
else {
[myButton setEnabled: NO];
}
}
But nothing happens...
I´ve tried to set the appDelegate as the NSTextfield´s delegate and added this method (myTextfield and myButton are IBOutlets):
- (void)textDidChange:(NSNotification *)aNotification
{
if ([[myTextField stringValue]length]>0) {
[myButton setEnabled: YES];
}
else {
[myButton setEnabled: NO];
}
}
That's the hard way, but it should work just fine. Either you haven't hooked up the text field's delegate outlet to this object, you haven't hooked up the myTextField outlet to the text field, or you haven't hooked up the myButton outlet to the button.
The other way would be to give the controller a property exposing the string value, bind the text field's value binding to this stringValue property, and bind the button's enabled binding to the controller's stringValue.length.
You could also give the controller two properties, one having a Boolean value, and set that one up as dependent upon the string property, and bind the button to that. That's a cleaner and possibly more robust solution, though it is more work.
Here's a solution using bindings.
Below I setup a NSTextField that is bound to the file owner's "text" property. "text" is a NSString. I was caught by "Continuously Updates Value". Thinking my solution didn't work but really it wasn't updating as the user typed, and only when the textfield lost focus.
And now setting up bindings on the button, simply set its enabled state to the length of the file owner's text property.
Annd, the working product.
If you use controlTextDidChange instead of textDidChange, you can get rid of the notification stuff and just rely on being the NSTextField's delegate.
Thanks Peter. What I missed (in my hard way version) is this piece of code in the awakeFromNib in the appDelegate:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(textDidChange:) name:NSControlTextDidChangeNotification object:myTextField];
It works perfect. Now I´m trying the easy way, but I´m afraid I´m not still good enough with the bindings.
To bind the property
#property (retain) IBOutlet NSString *aStringValue;
with the textfield´s value, what I have to use in IB for "Bind to:", "Controller Key" and "Model Key Path"?

Changing the value of an NSImageView when it is edited via the control

I have an NSImageView which is set to editable. I would like to be able to revert to a default image when the user deletes the image. I've tried changing the value that the NSImageView is bound to in the setter, but the getter is not called afterwards, so the NSImageView is blank, despite the bound value being set to another image. Here is the setter code:
-(void)setCurrentDeviceIcon:(NSImage *)newIcon {
[self willChangeValueForKey:#"currentDeviceIcon"];
if(newIcon == nil)
newIcon = [currentDevice defaultHeadsetIcon];
currentDeviceIcon = newIcon;
[self setDeviceChangesMade:YES];
[self didChangeValueForKey:#"currentDeviceIcon"];
}
How should I make the NSImageView update it's value?
You don't need to send yourself willChangeValueForKey: and didChangeValueForKey: messages inside of a setter method. When something starts observing your currentDeviceIcon property, KVO wraps your setter method to post those notifications automatically whenever something sends your object a setCurrentDeviceIcon: message.
So, the method should look like this:
-(void)setCurrentDeviceIcon:(NSImage *)newIcon {
if(newIcon == nil)
newIcon = [currentDevice defaultHeadsetIcon];
currentDeviceIcon = newIcon;
[self setDeviceChangesMade:YES];
}
And then you need to send this object setCurrentDeviceIcon: messages to change the value of the property. Don't assign directly to the currentDeviceIcon instance variable, except in this method and in init or dealloc (in the latter two, you should generally not send yourself any other messages).
If that's not working for you, either your image view is not bound, or it is bound to the wrong object. How are you binding it? Can you post the code/a screenshot of the Bindings inspector?
why you can use selector to NSImageview
example
-(IBAction)setImage:(id)sender
{
NSString *icon_path = [NSString stringWithFormat:#"%#/%#",[[NSBundle mainBundle]resourcePath],#"default_icon.png"];
NSData *imgdata = [NSData dataWithContentsOfFile:icon_path];
[[[self NSArrayController] selection] setValue:imgdata forKeyPath:#"currentDeviceIcon"];
}
Here NSArrayController is your array controller name.

How do I set the sender on a NSMenuItem's action?

The Apple documentation says that the sender passed to the NSMenuItem's action can be set to some custom object, but I can't seem to figure out how to do this. Is there a method I'm not seeing someplace in the documentation?
I'm not sure what piece of documentation you're referring to (a link would help).
You can use the -setRepresentedObject: method of NSMenuItem to associate an arbitrary object with a menu item:
//assume "item" is an NSMenuItem object:
NSString* someObj = #"Some Arbitrary Object";
[item setRepresentedObject:someObj];
[item setAction:#selector(doSomething:)];
Then when the menu item sends its action message you can obtain the object:
- (IBAction)doSomething:(id)sender
{
NSLog(#"The menu item's object is %#",[sender representedObject]);
}

Resources