Drag-and-drop files onto an NSTableView? - objective-c

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.

Related

Using separate .nibs and getting logic out of App Delegate

I have a document based app using the standard template. I have two auxiliary panels in Main Menu.xib, and my main logic is currently in the App Delegate, mainly through an IBAction in App Delegate triggered by a button on one of my panels. Everything works fine, but I know it should be organised better.
I have implemented a Preferences panel as suggested by Hillegass in Chapter 12. So:
Create a custom controller called AppController containing instance
of PreferenceController. This is instantiated in Main Menu.xib
Custom PreferenceController class which is subclass of
NSWindowController. This loads the Preferences.xib
Preferences panel created in Preferences.xib
Before I get too far in the app’s development, I want to be sure I’m organising things the right way.
I want to move my main logic out of App Delegate, possibly into App Controller. I want App Controller to be in charge of showing and hiding the various panels, and I want each panel to have its own .xib.
I have created two more subclasses of NSWindowController and made them ivars of the AppController, alongside the PreferencesController eg. Panel1Controller & Panel2Controller.
My problem is that interface builder is not letting me connect an IBAction in AppController to a button on one of my panels. It only lets me connect to the .xib file’s owner, i.e. Panel1Controller in the case of Panel1.xib.
If I put the logic in Panel1Controller, how do I get at one of the other panels (say Panel2Controller?) in order to hide it?
Am I going about this the right way?
Getting very confused….
Any help much appreciated!
Regards,
John
Just for simplicity sake I'd move all the nib elements controlled by the NSWindowController sub-classes out of the main nib and into nibs with the same name as the (NSWindowController) sub-classes that control them. DON'T expose IBOutlets or IBActions in the sub-class headers (they should be in a class extension ("#interface MyWindowController ()") in the source file for that sub-class.
Also, is the AppController a 2nd app delegate? Probably not what you want (there can only be one); you should merge its logic into the existing app delegate if that's the case.
I just came across this method.
This seems to do away with NSWindowController altogether, and make the AppController the file's owner of both .xibs. This way IB allows you to create outlets in AppController for each window, and contain actions.
I have created a very simple, two-window app using this method that hides one window when a button on the other is pressed. Before I go away and re-organise my main app, I want to be sure I'm doing this the correct, standard way, if there is one?
This page contradicts this method, by saying one window = one .xib + one NSWindowController subclass.
If you do it the latter way, how can one window talk to another, when you can't create outlets/actions in the AppController? Actions implemented in a window's NSWindowController class can't see outlets of another window, so how can they communicate?
This seems like pretty standard, basic stuff and yet I cannot find any sources which say which way is correct/best practice.
Another method I have read about here mentions using Notifications.
I'm still wondering though - which is the most common "accepted" method of loading two or more windows in separate .nibs and getting them to talk to each other? I'm surprise this info has been so hard to find.
If you're going to follow this pattern, separate AppDelegate and AppController, then your MainMenu.xib should not contain any window objects of any kind...it should just contain the application menu. Each additional window (NSWindow/NSPanel, etc.) gets its own .xib and its own NSWindowController.
There are two ways to assign references to your properties (IBOutlets) and methods (IBActions): 1) programatically, 2) via Interface Builder. Let's cover the second method!
To be able to wire things up from Interface Builder (IB) you will need a reference to the target object inside IB. For the MainMenu.xib file, this gets setup automatically: the MainMenu.xib contains an "AppDelegate" Object reference. The Object reference exposes the properties and methods in the AppDelegate class that are prefixed by the "IBAction" and "IBOutlet" macros. I write Object (with a capital O) because it is a widget available in the "Object Library" in IB.
You can easily create an instance of a custom objects inside a .xib file (via IB) by dragging an "Object" widget from the Object Library into your .xib. Then set the Object's class to that of your custom class. Once you've done this, the IBActions and IBOutlets in your custom class object will be available in IB. [Note: one thing to remember when doing this, is that when you load the xib, the object will be instantiated automatically. No need to alloc and init from within AppDelegate...you still have to call showWindow: on it].
As you mentioned, another approach is to simply have all of your additional .xib files owned by the AppController. That would be convenient, but it also gets 100% away from the architecture that you were trying to follow in the first place. In other words, if you're going to follow that style, why not just skip the separate AppDelegate and AppController in the first place, and just stick with the former (which would then be a Controller and Delegate).

NSOutlineView subclass not firing data source drag and drop methods

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!

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.

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.