How to create a binding for NSApp.dockTile's - cocoa

In IB it is easy to bind a label or text field to some controller's keyPath.
The NSDockTile (available via [[NSApp dockTile] setBadgeLabel:#"123"]) doesn't appear in IB, and I cannot figure out how to programmatically bind its "badgeLabel" property like you might bind a label/textfield/table column.
Any ideas?

NSDockTile doesn't have any bindings, so your controller will have to update the dock tile manually. You could do this using KVO which would have the same effect as binding it.
Create a context as a global:
static void* MyContext=(void*)#"MyContext";
Then, in your init method:
[objectYouWantToWatch addObserver:self forKeyPath:#"dockTileNumber" options:0 context:MyContext];
You then have to implement this method to be notified of changes to the key path:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == MyContext) {
[[NSApp dockTile] setBadgeLabel:[object valueForKeyPath:keyPath]];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Make sure you remove the observer when the controller object goes away.

If NSDockTile does support bindings, you can use the method bind:toObject:withKeyPath:options: to set up bindings on the badgeLabel property. Check the documentation for details on which arguments to use. If it doesn't work, you could either implement key value observing in your controller class and update the label each time the value changes, or even override NSDockTile to create a bindings compatible subclass.

I've tried lots of variations of bind:toObject:withKeyPath:options: on NSDockTile, on a controller, on the data source. I can't figure out a combination that works. Alternately, is there a way of having a BatchController object that can be bound to the data source, and it then updates the badge? How do I take an NSObject and make it bindable?

Related

KVO: not receiving notifications on NSTableView's -selectedRowIndexes?

I'm trying to have a custom subclass of NSTableView observe the value of its own -selectedRowIndexes property, and I'm having trouble figuring out how to receive the notifications properly. My subclass looks like this (using ARC):
#implementation MyTableView
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addObserver:self forKeyPath:#"selectedRowIndexes" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)dealloc {
[self removeObserver:self forKeyPath:#"selectedRowIndexes"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"change: %#", change);
}
#end
However, I never see -observeValueForKeyPath:... get called. Am I missing something?
I'm also open to a better solution - the reason I want to do KVO rather than relying on the delegate's -tableViewSelectionDidChange: method is that I'd like both the previous and current values for selectedRowIndexes, rather than just being able to get the current selection. If there's a way to do that without KVO on this property, I'm all ears.
If you're not seeing KVO notifications, I would open a radar at bugreport.apple.com. The reason is likely that they're not fully KVO compliant. I haven't tested, but I wouldn't be shocked.
As to how to do this without KVO, that's fairly straightforward. Use tableView:willSelectRowAtIndexPath: tableView:shouldSelectRow:. Check the current value, and the value to be added. Return YES.
I had the same problem, and I found the solution :
Bind the NSTableView view Selection Indexes to the array controller, key selectionIndexes

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.

How does an NSView subclass communicate with the controller?

I am brand spanking new to Cocoa programming, and am still kind of confused about how things wire together.
I need a pretty simple application that will fire off a single command (let's call it DoStuff) whenever any point on the window is clicked. After a bit of research it looks like subclassing NSView is the right way to go. My ClickerView.m file has this:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
}
And I have added the View to the Window and have it stretching across the whole thing, and is properly writing to the log every time the window is clicked.
I also have my doStuff method on my controller (this could be refactored to its own class I suppose, but for now it works):
- (IBAction)doStuff:(id)sender {
// do stuff here
}
So, how do I get mouseDown in ClickerView to be able to call DoStuff in the controller? I have a strong .NET background and with that, I'd just have a custom event in the ClickerView that the Controller would consume; I just don't know how to do that in Cocoa.
edit based on Joshua Nozzi's advice
I added an IBOutlet to my View (and changed it to subclass NSControl):
#interface ClickerView : NSControl {
IBOutlet BoothController *controller;
}
#end
I wired my controller to it by clicking and dragging from the controller item in the Outlets panel on the View to the controller. My mouseDown method now looks like:
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouse down");
[controller start:self];
}
But the controller isn't instantiated, the debugger lists it as 0x0, and the message isn't sent.
You could either add it as an IBOutlet like Joshua said, or you could use the delegate pattern.
You would create a Protocol that describes your delegate's methods like
#protocol MyViewDelegate
- (void)doStuff:(NSEvent *)event;
#end
then you'd make your view controller conform to the MyViewDelegate protocol
#interface MyViewController: NSViewController <MyViewDelegate> {
// your other ivars etc would go here
}
#end
Then you need to provide the implementation of the doStuff: in the implementation of MyViewController:
- (void)doStuff:(NSEvent *)event
{
NSLog(#"Do stuff delegate was called");
}
then in your view you'd add a weak property for the delegate. The delegate should be weak, so that a retain loop doesn't form.
#interface MyView: NSView
#property (readwrite, weak) id<MyViewDelegate> delegate;
#end
and then in your view you'd have something like this
- (void)mouseDown:(NSEvent *)event
{
// Do whatever you need to do
// Check that the delegate has been set, and this it implements the doStuff: message
if (delegate && [delegate respondsToSelector:#selector(doStuff:)]) {
[delegate doStuff:event];
}
}
and finally :) whenever your view controller creates the view, you need to set the delegate
...
MyView *view = [viewController view];
[view setDelegate:viewController];
...
Now whenever your view is clicked, the delegate in your view controller should be called.
First, your view needs a reference to the controller. This can be a simple iVar set at runtime or an outlet (designated by IBOutlet) connected at design time.
Second, NSControl is a subclass of NSView, which provides the target/action mechanism machinery for free. Use that for target/action style controls. This provides a simple way of setting the reference to your controller (the target) and the method to call when fired (the action). Even if you don't use a cell, you can still use target/action easily (NSControl usually just forwards this stuff along to its instance of an NSCell subclass but doesn't have to).
you can also use a selector calling method,
define two properties in custom class:
#property id parent;
#property SEL selector;
set them in view controller:
graph.selector=#selector(onCalcRate:);
graph.parent=self;
and call as:
-(void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
[_parent performSelector:_selector withObject:self];
}

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

Key Value Observing and NSButton state

I'm trying to observe checkbox status and make appropriate changes in the app when checkbox status changes. In a window manager that manages the window with checkbox I have following observer setup:
- (void)awakeFromNib
{
[myCheckBox addObserver:self
forKeyPath:#"state"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
}
- (void)dealloc
{
[myCheckBox removeObserver:self forKeyPath:#"state"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"KeyPath: %#", keyPath);
NSLog(#"ofObject: %#", object);
NSLog(#"change: %#", change);
}
I also have wired up myCheckBox to file owner (which is window controller) to appropriate checkbox in the window. However when I run my app observeValueForKeyPath:ofObject:change:context: method is never called.
What am I doing wrong?
In -awakeFromNib check that myCheckbox is not nil. If it's nil then it's not connected properly in IB.
Edit: NSButton.state has the same value as NSButton.cell!.state, but it isn't Key-Value Observable. To be able to observe the value, you'll need to use the \.cell!.state key path.
Unless documented to be Key Value Observing compliant, you should not expect the accessors of a given class to implement KVO support.
Buttons do implement key value binding, so instead of observing the state property you might bind one of your boolean attributes to the button's value binding.

Resources