NSTableView Binding and Observers - cocoa

I am trying to add a new row to an NSTableview using bindings. According to a past post I was instructed to addObject on the array controller and the KVO should handle the notification to the view. However,
I get the following error:
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: accountArray
Observed object: <AppDelegate: 0x10011e3b0>
Change: {
indexes = "<NSIndexSet: 0x102915ba0>[number of indexes: 1 (in 1 ranges), indexes: (2)]";
kind = 2;
}
I have added this observer in my App Delegate:
[self addObserver:self forKeyPath:#"accountArray" options:0 context:#"myContext"];
I have also tried to implement the observerValueforKeyPath but when I debug my code never gets to this point.
What am I doing wrong?

You must implement that method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

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

NSOutlineView outlineViewSelectionDidChange

my NSOutlineView outlineViewSelectionDidChange method will not be called.
I set set the NSOutlineViews delegate to the class where the other methods such as
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
exist. But outlineViewSelectionDidChange will not be called on selecting an item.
Does anybody has an idea?
This notification is a bit odd, in that it is not automatically forwarded to delegates. Try adding an explicit registration to your initialization code, like this example:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController;
{
[super windowControllerDidLoadNib:aController];
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(outlineViewSelectionDidChange:)
name:#"NSOutlineViewSelectionDidChangeNotification"
object:outlineView];
}
Okay,
meanwhile i figured out that the "NSOutlineViewSelectionDidChangeNotification" will be thrown only within the notification object. So i had to subclass my NSOutlineView to catch the notification and pass it to the object where i need it.
Your own view needs to conform to the NSOutlineViewDelegate protocol like so..
#interface MyOutlineViewController : NSView <NSOutlineViewDataSource,NSOutlineViewDelegate> {
IBOutlet NSOutlineView *myoutlineview;
}
#end
you will have this methods in your implementation
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item;
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
where you setup your outlineview.
When loading this view -(void)viewDidLoad gets called and your predefined nib/xib file or your manual call will set your datasource to fill it depending on your logic.
Now in your -(void)viewDidLoad your myoutlineview needs to set its own delegate with
[myoutlineview setDelegate:self];
so your own View may know where to call its notification methods triggerd from selections and so on. So you can place your notification logic inside the same View class conforming to this protocol.
-(void)outlineViewSelectionDidChange:(NSNotification *)notification {
NSLog(#"selection did change");
}

How do you reload a UIView?

I have a UINavigationController where the user can go back/fourth. When the user goes back, I want that UIView to reload. ( I am actually using an OHGridView ). On my ViewWillDisappear, I do something like this:
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadOHGridView" object:self];
}
So when they go back, it will send a NSNotification to the OHGridView to refresh it's data. It get's called, but it get's an error Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DetailViewController reloadData]: unrecognized selector sent to instance 0x4b9e9f0
Here's how I set up my NSNotificationCenter (in my DetailViewController):
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReloadGridNotification:) name:#"ReloadOHGridView" object:nil];
}
- (void)ReloadGridNotification:(NSNotification *)notification{
[database executeNonQuery:#"DELETE * FROM images"];
[items removeAllObjects];
[self reloadData];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now you would think it would update, but I get that error... Please help!
Coulton
Actually, I wouldn't think that it would update. reloadData isn't the name of a documented method of UIViewController, and you don't seem to have implemented one yourself. I'm not familiar with OHGridView, but I perhaps that's the object to which you want to send the reloadData message.
So you can change the observer that you set up from self to your instance of OHGridView, or you can implement a method in your view controller called reloadData that in turn sends the appropriate reload message to your OHGridView.

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.

How to create a binding for NSApp.dockTile's

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?

Resources