NSOutlineView subclass not firing data source drag and drop methods - objective-c

I'm using the excellent NSOutlineView subclass PXSourceList in one of my applications. I'm trying to implement drag and drop to my PXSourceList instance. I have:
connected both delegate and data source outlets in IB to my controller
in awakeFromNib in the controller, set self as the delegate and data source
in awakeFromNib in the controller, registered for dragged types
in the controller, implemented the requisite writeItems: validateDrop: acceptDrop: and
namesOfPromisedFilesDroppedAtDestination: methods and declared them in the controller's .h file
For some reason, the drag and drop methods implemented in (4) are not firing at all. I've tried:
Placing log statements in the drag and drop data source methods - they never get called.
Putting a log statement in one of the other data source method that logs the registeredDraggedTypes of the PXSourceList instance - it always returns the proper drag types assigned in awakeFromNib.
Taking the PXSourceList view instance and unembedding it from all containing views except the NSWindow instance - no luck there either.
Copy-pasting data source code from my application to the sample app that comes with PXSourceList - it all works without modification.
Copy-pasting the working code from the example application into the SK source - it doesn't work.
So essentially I'm in a spot where all data source methods get called except the drag-and-drop methods. It's behaving like I haven't registered for dragged types, but 1) I know I have and 2) the instance responds that it is registered for the dragged types that I set.
Any ideas?

Unfortunately, this is a side-effect of how PXSourceList is implemented; if you look inside PXSourceList.m, it makes itself the delegate and data source of itself (since it inherits from NSOutlineView), implements all of the outline view delegate and data source methods, and when each of these is called, it invokes the implementation of the actual delegate and data source which is being used by PXSourceList with the PXSourceListDelegate and PXSourceListDataSource methods. The reasoning behind this when I built PXSourceList was to have a consistent API rather than mixing and matching NSOutlineViewDelegate/DataSource methods with PXSourceListDelegate/DataSource's additional methods (for badges and icons etc).
The 10.7 SDK (which I assume you're using) added some extra drag and drop methods to NSOutlineViewDataSource. Of relevance here in particular, NSOutlineViewDataSource got the additional method -outlineView:pasteboardWriterForItem: added to it, which is an alternative to -outlineView:writeItems:toPasteboard:.
When you start a drag, NSOutlineView queries the data source (by using -respondsToSelector:) to determine which of these methods it implements and which of these to invoke. Given that PXSourceList implements both, and calls the corresponding -sourceList:... methods on the actual data source, NSOutlineView sees both of these methods as being implemented (even if they're not by your data source), and it seems like NSOutlineView chooses to call -outlineView:pasteboardWriterForItem: if both are implemented. Given that you don't have an implementation of sourceList:pasteboardWriterForItem:, the implementation of -outlineView:pasteboardWriterForItem: returns nil and your other methods don't get called (you can see the code here.)
To cut a long story short...
It looks like for now you'll have to implement -sourceList:pasteboardWriterForItem: instead of -sourceList:writeItems:toPasteboard: (or if you're targeting < 10.7, too, implement both; on 10.6 and below, -sourceList:writeItems:toPasteboard: will be called).
I actually have some improvements to PXSourceList in the works which uses the runtime and should fix problems like these, so keep an eye on the project on GitHub!

Related

Cocoa - explanation of view handling

I'm learning Cocoa at the moment, and have followed 'generic' tutorials on getting a basic form with a button and label.
With the .xib, I've added an 'Object' (instance of NSObject subclass), and have also created a ViewController class, which I connect to my view by setting Custom Class to ViewController. I then code up my ViewController.h and .m files adding a pressedButton method, and a label (myLabel). This all works OK (ie. the label updates when the button is pressed).
My question is: what am I actually doing with this process in C++ terms (a language I'm more familiar with)? As I understood it, my 'Object' (set to class ViewController) represents an instance of the .xib file (may be wrong here), and with this set to the ViewController class, I'm able to make outlets and actions in ViewController.h/.m, but I'm still not sure what's really going on behind the scenes.
Lastly, the XCode template provides an AppDelegate class 'free'. Given I'm managing my controls via my ViewController class, what would this file/object be used for - is it for application specific things that do not relate to the view itself, or is it also used to manage the controls on the form too (like you see in some tutorials I think)?
I hope, I understood the first part of your question well. The .xib (xml verison of nib file) does not represent a class or object. It is rather used to create a view object with all its subviews (each an object) and link it properly to your view controller.
It is most common and automatically generated that way, that the underlying view within your nib file corersponds to self.view (from your view controller's point of view).
You could access each view created by the nib file by navigating though the subview hierarchy of self.view. Alternatively you could define tags within nib/IB (Interface builder) to access individual view objects more easily.
"Just for your convenience" the IBOutlets were introduced. You can define an IBOutlet in the interface of your view controller and link it using IB to the dedicated object that is created using the xib file.
I am saying "Just for your covenience" because it is not really neccessary, considering other ways of addressing your view objects. But it is more than convenient and therfore strongly recommended and stimply the standard way of doing it.
You could even add further views programmatically to a form/view that was created using IB. You can change properties of those views programmatically.
The view and the subvies are created in that very moment when your view controller is initialized with a nib file using the initWithNibName:bundle: method. Then the nib file (xib these days) is opened, its content is parsed and interpreted and all views are created and added to their superviews respectively and their properties and action: selectors etc. are set accordingly. Again, the whole process is designed for your convenience. You could even create all the views etc. programmatically - witout any nib file at all. The loadView method of your custom view controller would be the appropriate place of dong so.
Second question:
AppDelegate is designed for application wide actions etc. If you can manage everything fine within your view controllers then you would not need to change anything on the AppDelegate.
It is often used as a container for variables or functions of global character (which are then properties and members of the app delegate object). Sometimes you may see it neccessary to override some of the AppDelegates methods like application:didFinishLanuncing or applicationDidBecomeActive.
Strictly spoken, UIApplicationDelegate is no class that you subclass. It is a protocol that you implement. (Very much like interfaces within Java - sort of overcoming the lack of multiple inheritance that you are familiar with from C++).

Separate Delegate and Controller

As many other developers new to Cocoa, I struggle with delegate and controller concept. I get the basics, but one thing bugs me. Practically every explanation says that "usually" or "in simple cases" (which are the only ones they give as examples) controller and delegate tend to be the same object.
That leads to a question: when would you want to separate controller and delegate for the same interface object?
Two general cases when using a separate class for your delegate is needed are
When you need to perform unrelated actions in response to the same delegate message, or
When you would like to share the logic of the delegate among multiple views or controllers.
An example of the first situation would be a page with two unrelated tables. Each UITableView would need its own delegate, so using the controller as the delegate would require an ugly if statement in each delegate method; defining and using separate delegates is clearly preferred in this case.
An example of the second situation would be a group of similar pages that show DB data from tables of similar structure. Pages themselves are sufficiently dissimilar, so you cannot reuse the controller in its entirety. If you choose to put the delegate into the controller, most of the logic behind the table view's data source would be identical. You can put the code into a shared delegate implementation, and have each controller instantiate that delegate with the configuration parameters specific to the table associated with this controller.
One note to keep in mind when using another object besides the controller as your delegate: the controller should retain / keep a strong reference to the delegate, since the view will only keep a weak / assign reference to it. See property "assign" and "retain" for delegate for more information on this.

"Global" UIImagePicker Functionality for Multiple Classes

I'm working on an app that (among other things) uses UIImagePicker to grab an image from the device once the user has selected the SourceType by tapping the appropriate button. Different sections of the app will need to use this functionality, as well as the variable holding the image information once selected. When I first started the project I had all of my code to do this in a single class named ViewController. I'm now working on moving the individual sections of the app into their own classes, but I'd like to be able to have them all use the UIImagePicker functionality from a central location.
Along with the necessary UIImagePickerController methods and protocols, I have a method that presents a view with buttons for each available SourceType. Each of these buttons then send a message to methods to show the appropriate picker (or the camera). Once an image is selected, it is applied to a variable for use by the different sections.
I wanted to get suggestions on the best way to approach this before I went to deep down the wrong rabbit hole.
Thanks!
If a lot of your classes use this functionality, you can create a superclass (itself being a subclass of UIViewController).
This class will expose some method to launch the process you described, and some other to gather the information collected.
If you don't want to use inheritance, or you already to with another class, you can also create a separate class responsible for this process.
This class, which is not necessary a UIViewController, has to be instantiated and then called the same way the superclass described above.

Drag and drop not working for NSTableView

I have implemented drag and drop feature in my application. All the functionality are well but when we drag a image in to NSTableView,
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
method is not getting called.
Can any one tell me the reason why this method is not getting called?
Even if I implement this one also...
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
We need a lot more information.
"...but when we drag a image in to NSTableView"
What do you mean by "image", and where (what application) are you dragging this image from? For example, do you mean an image file (Picture.png) from the Finder that you are dragging to the table view in your application? Or from your own application are you dragging some image from one place to your table view?
Is this your own custom subclass of NSTableView? Because that's the only place that you will see -performDragOperation: or -prepareForDragOperation: being called. By default, NSTableView overrides those primitive NSDraggingDestination methods to implement its own tableview-oriented type of methods like Bavarious mentioned (-tableView:validateDrop:proposedRow:proposedDropOperation:, -tableView:acceptDrop:row:dropOperation:, etc.). If you are talking about the fact that these methods aren't being called in an NSTableView subclass, then remember what the documentation states for -prepareForDragOperation::
This method is invoked only if the
most recent draggingEntered: or
draggingUpdated: message returned an
acceptable drag-operation value
So, first, you need to make sure you've registered for the drag types you want, then you need to implement -draggingEntered.
If, on the other hand, you aren't talking about an NSTableView subclass, but an external controller class, then, yes, those performDragOperation: and prepareForDragOperation: aren't called for it. In other words, if you have a controller class, say, MDAppController, it's set to be the delegate and datasource of an NSTableView, the -performDragOperation: and prepareForDragOperation: of MDAppController won't be called. Those methods are meant for NSView-based classes. For that reason, NSTableView has the following method defined in the NSTableViewDataSource protocol: tableView:validateDrop:proposedRow:proposedDropOperation:. If you implement that in your controller class, it should be called, provided you've set the tableView up properly and it's been registered for the types of data you want.

Drag-and-drop files onto an NSTableView?

I have an NSTableView which I wish to allow users to drag-and-drop video files onto. When they drop the file, it'll get added as a row in the table view.
How would I go about doing this? Currently the tableview's takes its data from an Array Controller (which takes its data from a NSMutableArray)
I found this documentation, but cannot seem to make it work..
I have..
made a "TableCon" class (which I changed to inherit from NSTableView, not NSObject)
changed the NSTableView class to TableCon
set the NSTableView's delegate outlet to that class
called registerForDraggedTypes in TableCon's init
implemented - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender; (again in TableCon)
..but, nothing, it acts like I never changed anything (no errors), what am I doing wrong?
Edit: I've tried implementing Boaz Stuller's suggestion, and also found this description of the solution (the first reply includes the solution to the first post). So what I have done now is..
Subclass NSArrayController which feeds content to the table view (TableListCon)
Add tableView outlet to TableListCon (and pointed it at the NSTableView)
Implement validateDrop, writeRowsWithIndexes, and acceptDrop in TableListCon
Called registerForDraggedTypes on the tableView outlet.
Again, no errors/warnings, but only the awakeFromNib method seems to be called (None of the other methods are called)
NSTableView handles drag-and-drop differently from generic views, which is overall a good thing. It means that you don't have to manually handle the complicated highlighting, cell tracking and inserting behaviours that tables require.
A description of what is required can be found here. Basically, you still call -registerDraggedTypes: (generally in your -awakeFromNib method) but instead of implementing the NSDraggingDestination methods, you implement the various data source methods associated with drag and drop, which can be found here. You should not need to subclass NSTableView to implement drag-and-drop in this fashion.
Note those are data source methods. You need to hook the table view's dataSource outlet to the class that implements those methods in order for them to be called.
In addition to what Boaz said, it sounds like you're creating an NSTableView subclass and then making an instance of that subclass the delegate of NSTableView. If you're going to subclass, that subclass should be used in place of NSTableView, not in addition to it. Also, it's almost always a violation of concerns to have a view be a delegate for another object.