Getting NSArrayController item for right click in NSCollectionView - objective-c

I'm trying to create a file explorer using nscollectionview and am currently implementing a right click menu for each item (i.e. copy/delete/rename/etc). I currently have:
An NSCollectionView linked with an NSArrayController which holds a custom object
A subclass of NSBox as the view for each item, this also tracks mouse events and passes them to the controller
The controller has an NSMenu outlet (rcMenu) and also an NSView outlet (itemView) for the NSBox subclass that should be where the menu popup
The code for calling the menu is:
[NSMenu popUpContextMenu:rcMenu withEvent:event forView:itemView];
Once run, this works in that the menu pops up when right clicking the item in the collection view, but on inspecting the event that's passed to the controller, there's not really anything I could use to find out which item was right clicked other than the x,y coordinates (which seem to be for the NSWindow rather than the item or NSCollectionView). What I really want is the object in the NSArrayController that had it's view right clicked.
Is this down to me setting it up incorrectly, is there an easy way to figure it out, or is it just that tough to work it out?

You might try setting the menu of each collection view item's view. Most likely, you'll do this by overriding +defaultMenu in your item view class. Once you do that, comment out the popUpContextMenu:withEvent:forView: message and see whether you can get away without it.
Furthermore, it would then not be too hard to serve up different menus for different kinds of items (e.g., folders vs. packages vs. files, and different types of files at that). You'd probably have to override -menuForEvent: instead of +defaultMenu.

I found an other solution that might help.
For this solution I made a subclass of NSCollectionViewItem and NSView, respectively (and for the ease of explaining) ItemViewController and ItemView.
I'm assuming you work with IB where you have already bound your NSCollectionView to the ContentArray of your NSArrayController (also bind the selectionIndexes).
Next add an ViewController object to the NIB and make sure its custom class is set to the ItemViewController. Now connect it to the itemPrototype outlet of your NSCollectionView.
Next add a Custom View object to the NIB and set its custom class to ItemView. Connect its outlet to the view property of your ItemViewController.
In the interface file of ItemView create a representedObject-like property. With this I mean something like:
#property (nonatomic, assign) id someRepresentedObjectPropertyName
This will be the property which will represent the item in your NSArrayController.
Now go to the implementation file of ItemViewController and override the -setRepresentedObject: method. In here we will first let the ItemViewController handle setting its representedObject, afterwards we assign the same representedObject to the property we made in ItemView. The override would look like:
-(void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
//Do some appropiate checking on the representedObject...
if (self.view != nil) {
[(ItemView *)self.view setSomeRepresentedObjectPropertyName:self.representedObject];
}
}
Now if you go back to the implementation of ItemView you can override the method -rightMouseUp: and build/set-up a NSMenu there and use the -popUpMenuPositioning...: method. The someRepresentedObjectPropertyName property of ItemView should be set to the correct item in your NSArrayController.
EDIT:
Instead of overriding -setRepresentedObject you could also bind the ItemView's someRepresentedObjectPropertyName to representedObject.someRepresentedObjectPropertyName

Related

NSButton binding to NSCollectionViewItem: representedObject?

When I pre-added a button to the NSCollectionViewItem's view, I can bind its action in the inspector:
Which works without problem.
Now I'd like to create that button programmatically within the view's mouseDown:, what should I assign to the bind: option for the same result as the "Bind to: Collection View Item" in inspector?
Here's the code: (Swift)
aButton!.bind(
"argument",
toObject: ???, // <- what should I set here?
withKeyPath: "representedObject",
options: options
)
EDIT: I was able to do it by subclassing NSCollectionView then override newItemForRepresentedObject: to assign the representedObject to the subclass view.
Still like to know if there are ways without subclassing NSCollectionView.
You should bind to the NSCollectionViewItem instance which owns the view of which the button is a descendant.
From what context are you creating the button and trying to bind it? Is this in a controller of the collection view? Or is it in the collection view item itself (which is a controller of the collection view item view)? Or perhaps it's in custom view class, although that would be a bit strange.
From the controller of the collection view, you can use -itemAtIndex: to get the relevant collection view item.
From the collection view item, you would just use self. However, in this case bindings don't really get you much. You might as well just set the button's target and action and do something with the representedObject in the action method.
If you're doing this from a view, then you need a way to obtain a reference to the collection view item. You should add a weak outlet on the view that you connect to the collection view item in the NIB. Then, you would use that outlet to get the collection view item for that bind() call.

How to bind click action of NSButton in view based NSTableView

I have an NSTableView that is set to be 'view based', and within each NSTableCellView there is an NSButton and an NSTextField.
The text field is being populated correctly from an array controller. The buttons are appearing correctly but I'm having trouble working out how to hook up the click action.
I thought this would be possible by control-dragging from the NSButton in IB to a simple method like this one in my controller (in this case an NSDocument subclass):
- (IBAction)testAction:(NSButton *)sender {
NSLog(#"Test action");
}
It connects fine but never gets fired. Any ideas why this is or how to fix it?
I don't understand why this works, but I had the same problem and was able to get it working by assigning the table delegate and datasource to the file owner within IB, which is also the class of my click handlers. Only then did it seem to actually bind the click handlers for the buttons in my cell view. Previously I was setting the delegate and datasource in code after the view was loaded.
You have to subclass NSTableCellView class. put your onClick Action method in subclass files.
Let me know if i am not clear..

Highlighting selected item in a NSCollectionView using NSBox

How do I make the NSCollectionView update to show the currently selected item using an NSBox? Displaying selection in a list seems like a basic thing, but I'm having all kinds of trouble with this.
I've read this question and also looked at the sample code from Apple. There seems to be several ways to do this.
Using a subclasses of NSCollectionViewItem and special "prototype view".
Using a NSBox.
I wish to use the NSBox way since it seems simples and is also used in the official code sample.
It's apparently done as described in the following quote by alternegro:
If a different background color will suffice as a highlight, you could
simply use an NSBox as the root item for you collection item view.
Fill the NSBox with the highlight color of your choice. Set the NSBox
to Custom so the fill will work. Set the NSBox to transparent.
Bind the transparency attribute of the NSBox to the selected attribute
of File Owner(Collection Item) Set the value transformer for the
transparent binding to NSNegateBoolean.
I'm stuck at the very first part: "use an NSBox as the root item for you (sic) collection item view". I've tried to change the "Custom Class" to a FoobarBox that inherits from NSBox, but it doesnt seems to help as I cannot change the background color to blue nor can I bind the transparency. Any pointers on how to make the selection display in my NSCollectionVuew would be appreciated.
First, create a class for your ListView that inherits from NSBox
#interface MyListViewBox : NSBox
#property (unsafe_unretained) IBOutlet NSCollectionViewItem *controller;
#end
Then, in Interface Builder, specify your class name as "Custom class" property as shown on my screenshot
Then you will realize IB does not show NSBox properties or binding in the GUI (at least with version 4.5.2), so I decided to change the properties programmatically.
Create an outlet for NSCollectionViewItem in your NSBox subclass (as seen above)
Use IB to link the outlet to your NSCollectionItemView
in -(void)awakeFromNib for your NSBox subclass, add the following code
-(void)awakeFromNib {
//properties are not showing up in XCode Inspector IB view
//configuring the box here :-(
self.boxType = NSBoxCustom;
self.borderType = NSLineBorder;
self.fillColor = [NSColor selectedControlColor];
//bind the "transparent" property of NSBox to the "selected" property of NSCollectionViewItem controller
//controller is bound as IBOutlet in IB
NSValueTransformer* transformer = [NSValueTransformer valueTransformerForName:NSNegateBooleanTransformerName];
[self bind:#"transparent"
toObject:self.controller
withKeyPath:#"selected"
options:[NSDictionary dictionaryWithObjectsAndKeys:transformer, NSValueTransformerBindingOption, nil]];
}
In XCode 4.5.2, you can just delete the NSView that comes automatically with the NSColletionView and drag in an NSBox (which will have all the appropriate bindings available). Make sure you re-bind the CollectionView to your new Box.

How to select one NSCell in an NSTableView?

I have a small NSTableView with a checkbox. Whenever the checkbox is not checked, I want one of the adjacent NSCells to be grayed out and inaccessible.
However, I can't figure out how to address only one specific cell. -dataCellForRow of NSTableColumn always changes the template cell for the whole table column.
How can I access one single cell?
Edit: I fill the table view using the NSTableViewDataSource protocol.
You don't "access a cell". NSTableView asks for data only when necessary, you don't populate it or control it directly.
Instead, you create a controller object which implements the NSTableViewDatasource and optionally NSTableViewDelegate protocols. The table view then sends the datasource messages to your controller and your controller supplies the appropriate data.
You can allow editing for an object displayed in the table view by implementing the ‑tableView:setObjectValue:forTableColumn:row: datasource method. This method will be called on your controller object when the user clicks the checkbox. It is your controller's responsibility to update the model appropriately.
When the model is updated, your controller should tell the table view to reload. The table view will then ask your controller for the value of any cell that requires display using the ‑tableView:objectValueForTableColumn:row: datasource method. This will include the cell that you need to disable. Your controller needs to supply the appropriate value for the cell.
If you need more control of the cell, you can implement the
‑tableView:willDisplayCell:forTableColumn:row: delegate method. This is called just before a cell is displayed, and you can modify the cell appropriately.
More info about using data sources is in the docs.
The other option (instead of using a datasource) is to use Cocoa Bindings and an NSArrayController that you bind to your collection of model objects. In that case, you can bind the Enabled binding of the table column to some property of your model object that controls the cell's enabled state. It is your responsibility to ensure that the state of that property is correct.
If you need to make the property dependent on the value of another property, you can use the dependent key mechanism outlined in the Key-Value Observing documentation.
Could you bind the editability of that column to the value that is being displayed in the checkbox? i.e. if it is checked, it is editable, otherwise it isn't?
I am trying to remember the exact editor interface, and I am not next to my Mac at home, so I am not able to do a total walk through on it - hope this can point you in the right direction.
Since SDK Version 10.7, there's -viewAtColumn:row:makeIfNecessary: on NSTableView. The majority of information I found on the web don't take the new methods into account, so here it is for all the others looking for an answer to this question.
From Mouse Event to Cell Selection
First, add a protocol for your controller to handle cell selection from a table view, like this:
#protocol XYZCellSelectionDelegate <NSObject>
- (void)cellViewWasSelectedAtRow:(NSInteger)row column:(NSInteger)column;
#end
Then subclass NSTableView and override -mouseDown:
// In your Custom Table View subclass:
- (void)mouseDown:(NSEvent *)event
{
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger selectedRowIndex = [self rowAtPoint:point];
NSInteger selectedColumnIndex = [self columnAtPoint:point];
if ([self.calendarViewDelegate respondsToSelector:#selector(cellViewWasSelectedAtRow:column:)])
{
[self.calendarViewDelegate cellViewWasSelectedAtRow:selectedRowIndex column:selectedColumnIndex];
}
[super mouseDown:event];
}
Afterwards, you can use -viewAtColumn:row:makeIfNecessary: like this in the delegate/controller object:
- (void)cellViewWasSelectedAtRow:(NSInteger)row column:(NSInteger)column
{
NSView *selectedView = [self.tableView viewAtColumn:column row:row makeIfNecessary:YES];
// Do something with the cell to the right
NSInteger nextColumn = column + 1;
NSView *cellNextToIt = [self.calendarTableView viewAtColumn:nextColumn row:row makeIfNecessary:YES];
}
Note: Nowadays, I'd pass the table view to the delegate as a parameter instead of relying on the delegate to keep a reference to the table view.

UINavigationController Push Views

Sorry - this may be an easy question, I'm new to iPhone development and still wrapping my head around Views vs ViewControllers.
I have a NavigationViewController and I can push Views using the following method in the RootViewController which is connected to a Bar Button Item:
- (IBAction)switch:(id)sender {
NSLog(#"Swith...");
LibraryViewController *varLibraryViewController = [[LibraryViewController alloc] initWithNibName:#"LibraryViewController" bundle:nil];
[[self navigationController] pushViewController:varLibraryViewController animated:YES];
}
I want to call this same method from a button on the same view that is currently loaded. Basically I want to have the Bar Button at the top call the same method as a button on the view. I was wondering how to call a method in the ViewController from the view loaded from that viewController. Hopefully that makes sense.
Do I need to create an instance of the RootViewController? I would think that would already be instantiated. Thank you.
BTW, the code you have pasted there is leaking your LibraryViewController. You need to either explicitly release it after pushing it, or autorelease it when it's created.
Your RootViewController should have its own xib file. In this xib, the RootViewController is represented by the object named "File's Owner". You can link buttons on the view to File's Owner the same way you can link things to RootViewController in MainMenu.xib.
You'll want to declare your method as an IBAction in your header file:
- (IBAction) myMethod: (id) sender;
Save your header, then switch to Interface Builder. Right click on the Bar Button, and drag from the selector tag to your view controller object (probably the File Owner). When you release, you should be given a popup menu of available actions, and myMethod should be selectable.
If you don't get this popup, you may need to make sure your File Owner class is set properly: select the File Owner in the file window, then select "Tools" > "Identity Inspector" from the menu. In the inspector, type your view controller's class into the Class field.