I have an NSTableView with a single column, which gets its data through an NSArrayController bound to a Core Data entity. The data feed works great, and I have been able to get drag and drop working by implementing the methods
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
as well as the specific drag and drop methods
– tableView:acceptDrop:row:dropOperation:
– tableView:writeRowsWithIndexes:toPasteboard:
But do I really have to implement the first two methods even though the tableview is fed data through the array controller? I tried commenting out my implementations, but then I get errors in the console saying "Illegal NSTableView data source". The documentation for the NSTableViewDataSource protocol says that the methods are optional if the application is using Cocoa bindings, so obviously, I'm doing something wrong.
The question: How do I make the tableview use its existing binding and still support drag and drop?
I believe you do need to implement them to silence the complaint. I believe when used with Bindings the values returned by these data source methods are ignored. So for -numberOfRowsInTableView: you can return zero; for -tableView:objectValueForTableColumn:row: you can return nil.
You do not need to implement these methods. It seems pretty shaky, but I silenced them by re-establishing the datasource connection in Interface Builder. I think perhaps if you add the datasource after you add bindings, it silences the warning.
Related
I've been developing on iOS for some time, but am very new to Cocoa development, and something seemingly very simple is stumping me.
I have an NSTableView, hooked up to a subclass of NSWindowController as both datasource and delegate. I have an array of "File" objects (my model class) and want to populate one column of my tableview with file types, and another with timestamps.
The dataSource methods are definitely being called, as verified by setting breakpoints. In fact, I end up with an appropriate number of rows that are selectable...but none of them display anything. I even tried returning an arbitrary string literal in objectValueForTableColumn, for all rows and columns, and still nothing.
I think I'm probably stuck on how tableViews work in iOS, but obviously they are very different here...I am used to configuring and returning a cell myself, but here we just pass AnyObject??? How exactly does the tableView know how to display AnyObject? I'm really struggling with the conceptual understanding here. Appreciate any help.
I have an NSArrayController bound to CoreData in my application. It is also bound to a TableView that displays the data. Two buttons are bound to the ArrayController that add and remove lines. All of this is working as expected. I can add, edit, save, and remove CoreData Entries.
There is a section of my app that is to accept drag and drop operations from files (working). It takes the data from the files, looks for various information, and is to insert this information into the Core Data database via the NSArray Controller.
I have added the class handling the parsing/adding of the file to the database as an object in IB. I created an IBOutlet for the array controller in the class, and bound the controller to the class' referencing outlet.
If I add a button to the interface to directly call the method that adds a custom record to the database, everything works. If the method is called via the drag and drop operation, nothing works, even logging a simple [arrayController className] returns null (though returns NSArrayController as expected when the method is called from the button click).
The only difference I can see is that when accessed through the button click, the method is called directly, while the other way passes through my drag and drop class before loading the parsing class, but I'm completely stuck on how to remedy this situation. I'll be happy to provide code, just not sure which code you'll need.
Any help is appreciated. Thanks!
==================
UPDATE
turns out I was connecting the IBOutlet to a class (a subclass of a view) object in IB instead of to the view itself handling the drops. Connecting these up made things work. Well, not work, I have other issues to iron out now, but the Array controller is now instantiated.
Moved from comment to answer: The array controller you are trying to add stuff is not instantiated. I assume you are not referring to your original NSArrayControllerinstance but maybe a new created one? Probably a problem of communication between your class instances.
Debugging this should be straightforward ... using the debugger. Set a few breakpoints (one at each action the button(s) call, and one at each point where your class instances are meant to talk to each other (your importer and your main controller)). Run, test, step through the code when the debugger breaks at each breakpoint.
My guess: An outlet is not hooked up (is nil) in IB or is not yet reconnected at runtime (see -awakeFromNib and make sure you're not trying to touch an outlet or action that hasn't been fully reconnected from the nib at runtime by the time you're trying to use it).
Something’s not hooked up right, BUT you don’t want to do it this way anyways. There’s no advantage to inserting via an NSArrayController. Just create new objects with NSEntityDescriptions:
+ (id)insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context;
And you’re done. If your NSArrayController is hooked up correctly it’ll auto-fetch the new objects at the end of the event so the user will see them “immediately.”
I need to create/use a NSTableView programmatically. From the documentation, it seems that I would implement the NSTableViewDataSource protocol. But the function tableView:objectValueForTableColumn:row: suggest (because of the row index) that I would have to manually take care of the sorting. Is that right? Also, as this is called on every redisplay, that might be slow because I am using Python and it would mean a Python call for every row/column.
I wonder wether it make sense to use Cocoa binding and wether that would be more simple. In any case, I would have to do that programmatically and I am a bit stumbled about how to that. From other examples, I guess I would create a NSArrayController and bind it all together somehow.
Also, I want to have it working on older MacOSX, so I guess I have to use the cell-based NSTableView, whatever that means.
The data source will be static and is not editable, i.e. I can just provide a NSArray with the data.
There are three ways to use NSTableViews: 1) delegate methods; 2) NSArrayController; or 3) Bindings. My best advice to you is to learn all three of these in Xcode on a Cocoa ObjC project first before attempting to do this in python. Note: I'd also recommend that you first learn how to do these via nibs and then figure out how to do it programmatically (again in Xcode on a Cococa ObjC project before attempting it in python).
If you understand how Interface Builder (view in Xcode 4, app pre-Xcode4) bindings work then for the following code "Bind To" corresponds to myController, "Controller Key" would be "selection", and the Model Key Path would be "fullPath".
[myView bind: #"valuePath"
toObject: myController
withKeyPath: #"selection.fullPath"
options: nil];
You just need to sort your array once, then when the delegate method is called access the appropriate index in the array.
You really should have a good read of the Table View Programming Guide.
In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.
I have an NSTableView and an NSOutlineView, both with their content provided by bindings, that I'd like to have some drag-and-drop functionality:
Drag rows from Table A onto a row of Outline B, where they will be copied into a data structure which the row in Outline B represents.
Drag a row from Outline B onto another row in Outline B, which will copy the data represented by the first row into the data represented in the second row.
I've read Apple's drag-and-drop documentation and gotten just about nowhere. It doesn't really seem to apply to what I need to do. What am I missing?
The page you linked to is pretty clear about what you need to do. In table A's data source, implement registerForDraggedTypes: and tableView:writeRowsWithIndexes:toPasteboard: to put some private TableAPasteboardType data on the pasteboard.
In outline B's data source, implement the same two methods and put some private OutlineBPasteboardType data on the pasteboard.
Finally, implement tableView:validateDrop:proposedRow:proposedDropOperation: and tableView:acceptDrop:row:dropOperation: to check the pasteboard for either TableAPasteboardType or OutlineBPasteboardType and make the appropriate changes to your bound model, depending.
It's pretty straightforward once you just plow in and do it.
You need a data source—AFAIK, you can't make this happen with Bindings alone.
The unfinished Adium Xtras Creator, which is under the BSD license, includes an array controller that you can set as the data source to get drag-and-drop in a Bindings-powered table view.
This requirement may not apply to NSOutlineView and NSTreeController. I haven't tried that.
In MacOS 10.7 some new protocols were added to implement this.
There is a lack of documentation for tables at the moment but you can find some nice examples:
DragNDropOutlineView
SourceView
TableViewPlayground
For NSTableViwew the Protocol NSTableViewDataSource defines the following methods:
(BOOL)tableView:writeRowsWithIndexes:toPasteboard:
tableView:validateDrop:proposedRow:proposedDropOperation:
tableView:acceptDrop:row:dropOperation:
For NSOutlineView the Protocol NSOutlineViewDataSource defines the following methods:
(BOOL)outlineView:writeItems:toPasteboard:
(NSDragOperation)outlineView:validateDrop:proposedItem:proposedChildIndex:
(BOOL)outlineView:acceptDrop:item:childIndex:
These are the minimum requirements to implement for each view type.
The use cases are quite similar.
If the toPasteboard: method returns YES, the drag is started.
The validateDrop: method controls which target node is allowed by updating the marker in the view
Return YES for the acceptDrop: method if the drop was successful
This lead to two sub-usecases you have to manage. The first one is a drag & drop within the same view or the same operation between two views. Additionally you may distinguish between move, copy or delete operations. A nice example is how the breakpoints work with drag & drop in Xcode.
The tableView has some additional methods to customize drag & drop, but the ones I mentioned are the key methods to get it working.