View-based NSTableView view controllers - cocoa

I'm not sure if I am doing things right but this is my problem:
I have a view-based NSTableView using bindings to an arraycontroller.
I need to do some custom drawing on each row, depending the represented object as well as capture click in certain areas so for this I would need to have a controller for each row and set outlets for the sub-views in my custom cell view, but I don't understand how I can achieve this.
If I just add an object to the nib and make the connections to it, then I cannot tell which of the views is being drawn (or has been clicked).

You have to implement the delegate methods :
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
It's used by the table view to get a view for a give cell (column, row).
Then by using "makeViewWithIdentifier:owner:", you can get the a reusable cell with a given identifier and a given owner (view controller).
The simplest way is to design your cells in Interface Builder, and set a different identifier for each one. Then the method "makeViewWithIdentifier:owner" will automatically create a view for you for the given identifier.

I just found someone asked a similar question and the answer to it also satisfies my needs, so for anyone ending up here, this is what I did:
I set my NSTableCellView controller as the delegate of the NSTableView.
In my NSTableCellView subclass I implement the needed methods (drawRect:, mouseUp: and so forth) and call the respective methods in the controller.
To access the controller I get the NSTableView and then its delegate like this:
NSTableView *tableView = (NSTableView*)myView.superview.superview.superview;
MyControllerClass *controller = (MyControllerClass*)tableView.delegate;
[controller view:myView drawRect:dirtyRect]
On the controller, to tell which view is sending an event, I use their identifiers.

Related

How can I tell programmatically whether a NSTableView is view-based or cell-based?

Should be an easy question, but there's nothing in the interface.
Apart from seeing whether something like preparedCellAtColumn:row: throws an exception, is there anything else one can do?
Except that preparedCellAtColumn:row: doesn't throw an exception, it just logs a message, and returns an NSCell object, so you can't test it for nil.
Funny that several people say that there’s no need to know when the questioner obviously does have that need. And there are good reasons you might want to know this; e.g. if you implement a generic NSTableView subclass or delegate, you must differentiate its behavior dependent on whether the table view is view- or cell-based.
If you use an NSArrayController and bindings, an easy way is to check for NSTableColumn bindings, because cell-based NSTableViews do have these, and view-based NSTableViews do not. So this code fragment will work:
NSTableColumn *tableColumn = [[myTableView tableColumns] objectAtIndex:0];
NSDictionary *binding = [tableColumn infoForBinding:#"value"];
if (binding) {...} // cell-based table view
else {...} // view-based table view
I haven’t tried myself, but if you use an NSTableViewDataSource instead, you’d probably simply check whether the data source responds to - tableView:setObjectValue:forTableColumn:row: (cell-based table view) or not (view-based table view).
There is no need to tell it, if you use objectValueForTableColumn it will automatically become cell based and on the other side if you use viewForTableColumn then it will be view based. You can pass any type of view in both of these methods.

How to connect outlet in a table cell view?

I am using a view-based table, and I want to create an outlet for an element in a cell view. I cannot get the outlet to connect though... it's always nil.
Specifically, I have a NSProgressIndicator in a table cell and want to manipulate it in code.
Here's what I have so far:
I have created a subclass of NSTableView, with the corresponding outlet property:
#interface MyTableCellView : NSTableCellView
#property IBOutlet NSProgressIndicator *myProgressIndicator;
#end
#implementation MyTableCellView
-(void)awakeFromNib
{
// _myProgressIndicator is nil!
}
#end
And I have set the custom class in the nib. The existing NSTableCellView is replaced with MyTableCellView via the dropdown.
At this point, some observations:
If I Ctrl+Click and drag the progress indicator to connect this outlet, it is not shown.
Likewise, if I try to Ctrl+Click and drag the progress indicator using the assistant editor, I can only connect to the property via binding. It doesn't recognize this as a valid outlet.
However this outlet IS shown on the sidebar, with a warning that it doesn't exist:
I know MyTableCellView is being used. Breakpoint on awakeFromNib confirms this, and confirms that _myProgressIndicator is nil.
This is a sandbox project, with barely more than what I've described.
SO, how do I access this progress indicator from code?
I don't believe you should do it that way; instead:
Modify the model object used by the table view's data source to populate the table view.
Call the table view reloadData (or better reloadDataForRowIndexes:columnIndexes:).
Therefore you should only need an outlet to the table view to do this and any modification of the table view's cell objects should be done within the table view delegate and/or data source methods.
As described in the question, the usual method to connect outlets in Interface Builder does not work (maybe it is fixed in a recent version of Xcode, I am still using a version earlier than 6). Anyhow, it does work via the Connections Inspector.
Create an IBOutlet in your subclass of NSTableCellView.
In your XIB, from Identity Inspector, change the NSTableCellView to your subclass.
From Connections Inspector, drag from your outlet to the View object.

Click in view of NSCollectionViewItem

I'm new to Cocoa dev, so many concepts of it are not clear to me...
I'm trying to build a simple app which will use Flickr API to retrieve user photosets and show them in a NSCollectionView, by clicking them, will start to download the photos of the photo set.
I'm using Xcode 5.0.1 with latest SDK which is 10.9
After reading some articles about how to use binding to deal with NSCollectionView, I'm now facing another problem regarding handling events in NSCollectionViewItem.
Per I understanding, mouse events can be easily handled by implement
-(void) mouseDown:(NSEvent *)theEvent
In a NSView subclass, say
#interface MyViewController : NSView {
}
And assign the view custom class to the subclass I made (MyViewController) in InterfaceBuilder.
Now, I have no problem to do as above, and the mousedown did handled as expect in most of widgets.
The problem is, I have a NSCollectionViewItem subclass as below:
#interface MyItemController : NSCollectionViewItem {
}
I'm trying to implement mousedown method there, this class was set to as File's Owner in a separated nib file. And the view will be automatically load when the NSCollectionView loaded.
Now, MyItemController cannot be as customer class in the view object in IB which is obviously because of it is not a NSView subclass but a NSCollectionViewItem subclass.
If I write a subclass of NSView and make the custom class of view object, I can get the mousedown.
However, I cannot get the representedObject and index of NSMutableArray in this approach and they are the essential information I need.
So my question is, what is the right way to deal with mouse events view of NSCollectionViewItem?
My code in GitHub here:
https://github.com/jasonlu/flickerBackupTool
Thanks!
UPDATE
I found a approach to solve this problem is by subclassing NSView and implement mousedown and use super, subviews to get and index and the array itself
- (void)mouseDown:(NSEvent *)theEvent {
NSCollectionView *myCollectionView = (NSCollectionView *)[self superview];
NSInteger index = [[myCollectionView subviews] indexOfObject:self];
NSLog(#"collection view super view: %#",myCollectionView);
NSLog(#"collection index: %ld",index);
NSLog(#"array: %#", [[myCollectionView content] objectAtIndex:index]);
}
It seems work, but I'm not sue if this is the best practice, it looks like depends on view too much and took a long way to reach the array.
I wouldn't bet that NSCollectionView always creates all subviews (subviews which are far away from the viewing area might be delayed and/or reused). Therefore, I wouldn't rely upon subview searching.
Overload NSViewController to create an NSView so that the representedObject assigned to the NSViewController is accessible from the NSView. From there you could search the actual content for index determination.
Overloading NSCollectionView and recording the actual index during view creation would probably not work well because a deleted item probably doesNot re-create any views.

Programmatically update a TableView that is governed by Cocoa Bindings

I'm fairly new to obj-c and cocoa so please bear with me:
I have a NSTableView set up with cocoa bindings which works as expected with the simple -add -remove, etc methods provided by an instance of NSArrayController in my nib. I would like to programmatically add objects to the array that provides content for this controller (and hence for the table view) and then update the view accordingly.
I current have a working method for adding a new object to the array (verified by NSLog) but I can't figure out how to update the table view.
So: How do I update the bound tableview? (ie, after I have programmatically added objects to my array). I'm essentially after some view refreshing code like [view reloadData] in glue code, but I want it to work with the bindings I have in place.
Or is there a KVC/KVO related solution to this problem?
Code Details:
AppController.h
#interface AppController : NSObject
#property NSMutableArray *clientsArray;
-(IBAction)addClientFooFooey:(id)sender;
#end
AppController.m (note, I also have the appropriate init method not shown here)
#implementation AppController
...
-(IBAction)addClientFooFooey:(id)sender{
[self.clientsArray addObject:[[Client alloc] initWithFirstName: #"Foo" andLastName:#"Fooey"]];
//Need some code to update NSTableView here
}
#end
Client.h just simply defines two properties: firstName and lastName. The 2 columns of an NSTableView in my mainmenu.nib file are appropriately bound to these properties via an array controller bound to my AppController instance.
On a side note/as an alternative. How could I add functionality to the existing NSArrayController method -add, ie, something like: -addWithFirstName:andLastName and still have this compatible with bindings?
You have two main options for doing this provided your array controller is bound to clientsArray.
The first way is to just use the array controller's addObject: method instead of adding objects directly to clientsArray.
The other way is to keep your current addClientFooFooey: method but wrap your existing code with these two lines:
[self willChangeValueForKey:#"clientsArray"];
[self didChangeValueForKey#"clientsArray"];
This tells the KVO system that you are making a change to the array so it will go and look at it again.
The first option is the most straightforward, but if for some reason you need to update the array directly just let KVO know you are doing it.

cocoa + context sensitive menu on NSTableView with multiple rows selected

i am having a problem displaying context sensitive menu on control click on a tableview when multiple rows are selected.
Its working fine when a single row is selected and then control clicked on it.
The way i am implementing this is shown below:
-(void)doSingleClick
{
NSLog(#"single clicked");
if([[NSApp currentEvent] modifierFlags] & NSControlKeyMask)
{
NSLog(#"control clicked.......");
[NSMenu popUpContextMenu:[self showContextMenu] withEvent:[NSApp currentEvent] forView:tableView];
return;
}
}
and showContextMenu function returns a NSMenu object.
I am dong it this way as my table view for some strange reason does not recognize mouseDown or mouseUp or menuForEvent events.
the problem with the above code segment is, when multiple rows are selected and control clicked, it does not recognize the control click and does not go into that loop and hence not displaying the context menu.
Please suggest me a mechanism to achieve this.
Thanks
I don't recommend the approach that is given in the answers above. Instead, look at the "DragNDropOutlineView" example in Leopard and higher. That, and the release notes, give a proper way to implement contextual menus for a single row, or multiple rows. This includes having AppKit automatically do the proper highlighting.
corbin dunn
(NSTableView Software Engineer)
i hve tableviewcontroller class which is a subclass of NSTableView.
That's very bad naming and suggests that you are not architecting your application properly. Views aren't controllers. Keep them separate.
but this class in which i implemented menuForEvent method but its not getting called for some reason.
Did you make your table view an instance of this class in Interface Builder? If not, your instance is still an NSTableView, and the subclass you wrote is what Ian Hickson might call “a work of fiction”.
Corbin's answer is the best one here.
link text
I don't believe the action method is called when multiple rows are selected.
What would probably be a lot easier would be to override the menuForEvent: method in NSTableView. You'd have to create a subclass of NSTableView to do this, but it would be a cleaner solution.
You could also create an informal protocol (a category on NSObject) and have the NSTableView delegate return the appropriate menu.
#interface NSObject (NSCustomTableViewDelegate)
- (NSMenu *)tableView:(NSTableView *)tableView menuForEvent:(NSEvent *)event;
#end
#implementation NSObject (NSCustomTableViewDelegate)
- (NSMenu *)tableView:(NSTableView *)tableView menuForEvent:(NSEvent *)event {
return nil;
}
#end
And in your NSTableView subclass:
- (NSMenu *)menuForEvent:(NSEvent *)event {
return [[self delegate] tableView:self menuForEvent:event];
}

Resources