Cocoa notification, how to observe for event? - cocoa

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.

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);
}

Change the null placeholder in a Cocoa binding?

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.

identify sender of controlTextDidEndEditing

I have a view with three NSTextFields connected to a View Controller that acts as delegate for all three of them.
I have successfully implemented the controlTextDidEndEditing to intercept the text entered by the user and change a property in my model. The method though is unique in the delegate and all the three textfields trigger it. The question is, how can I identify which one of them fired the notification? I can get the NSTextView from the key "NSFieldEditor" of the notification but that doesn't really tell me which one it is. At the beginning I thought of using the NSTextField placeholder but the the method returns me a NSTextView which doesn't seem to have a placeholder property.
Any idea?
You should be able to obtain the reference to the NSTextField via [notification object]. If all three of your NSTextFields are available as delegates, it is easy to check which one triggered the event.
Like this:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification {
NSTextField* textField = (NSTextField *)[aNotification object];
if (textField == textField1) {
// textField1 triggered the event
} else if (textField == textField2) {
...
} else if (textField == textField3) {
...
}
}

How to bind NSButton enabled state to a composed condition

This is my situation in Xcode Interface Builder:
There is also an NSArrayController in entity mode which controls the content of the NSTableView. I want to enable the 'Create' button when the NSTableView is empty (as controlled by the NSSearchField) AND when the text in the NSSearchField is not empty. How do I achieve that? Is it possible without programming?
To what KVO compliant values can I bind the 2 enabled conditions of the 'Create' button?
I don't think there's a way to do it entirely in interface builder, but with a small amount of code you can get it working pretty easily. First, make sure your controller (or App Delegate) is set as the delegate of the search field, and that it has IBOutlet connections to the search field, the button and the array controller. Here's how I would implement it:
// This is an arbitrary pointer to indicate which property has changed.
void *kObjectsChangedContext = &kObjectsChangedContext;
- (void)awakeFromNib {
// Register as an observer so we're notified when the objects change, and initially at startup.
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:NSKeyValueObservingOptionInitial
context:kObjectsChangedContext];
}
// This updates the button state (based on your specs)
- (void)updateButton {
BOOL canCreate = (searchField.stringValue.length > 0 &&
0 == [arrayController.arrangedObjects count]);
[createButton setEnabled:canCreate];
}
// This delegate method is called whenever the text changes; Update the button.
- (void)controlTextDidChange:(NSNotification *)obj {
[self updateButton];
}
// Here's where we get our KVO notifications; Update the button.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (kObjectsChangedContext == context)
[self updateButton];
// It's good practice to pass on any notifications we're not registered for.
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
If you're new to bindings some of that may look like Greek, hopefully the comments are clear enough.
I'm SOOO late for this, but came up with another method and just tested it in my app. It works, so I'm going to share it for anyone who will find this question in the future.
Basically what you want to do is to create a property WITHOUT a corresponding value in your controller
#property (readonly) BOOL enableProperty;
This means that there's actually no
BOOL enableProperty;
defined in the header file, or anywhere
then, rather than synthesize it, just write your own getter, and put there your condition
- (BOOL) enableProperty{
return (condition);
}
Third step: anytime there's the chance that your condition changes, notify it.
- (void) someMethod{
//.... Some code
[self willChangeValueForKey:#"enableProperty"];
[Thisline mightChange:theCondition];
[self didChangeValueForKey:#"enableProperty"];
//.... Some other code
}
fourth step: in IB, bind your control's enabled property to this "fake" property.
Enjoy! ;)
You seems to have a window, so presumably you have a controller object which is set as the File's Owner for the NIB file.
Why not declare a boolean property in this controller class, that returns a value based whatever conditions you want ?
#property(readonly) BOOL canCreate;
That you implement :
-(BOOL)canCreate {
// compute and return the value
}
Be sure to send KVO notifications appropriately when the conditions for the creation change.
The last step is to bind the button's enabled binding on the File's Owner canCreate key.

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"?

Resources