Can NSTableView handle normal drag-and-drop methods? - objective-c

I'd like to use NSTableView without the NSTableViewDataSource Methods but just like a normal view. draggingEntered: and draggingExited: are being called but when I return NSDragOperationCopy, I don't see the green plus mouse pointer and performDragOperation: doesn't get called.
I subclassed the NSTableView with these methods:
- (void)awakeFromNib
{
[self registerForDraggedTypes: [NSArray arrayWithObject: NSFilenamesPboardType]];
}
- (NSDragOperation)draggingEntered: (id < NSDraggingInfo >)sender
{
NSLog(#"draggingEntered"); //Gets called
return NSDragOperationCopy;
}
- (void)draggingExited: (id < NSDraggingInfo >)sender
{
NSLog(#"draggingExited"); //Gets called
}
- (BOOL)performDragOperation: (id < NSDraggingInfo >)sender
{
NSLog(#"performDragOperation"); //Doesn't get called
return YES;
}

The documentation says it conforms to the NSDraggingDestination and NSDraggingSource protocol, so yes it should hand normal drag-and-drop.
You might try using -registerForDraggedTypes: method from nsview.

Related

Drag and Drop from the Finder to a NSTableView weirdness

I'm trying to understand how best to impliment drag and drop of files from the Finder to a NSTableView which will subsequently list those files.
I've built a little test application as a proving ground.
Currently I have a single NSTableView with FileListController as it's datasourse. It's basically a NSMutableArray of File objects.
I'm trying to work out the best / right way to impliment the drag and drop code for the NSTableView.
My first approach was to subclass the NSTableView and impliment the required methods :
TableViewDropper.h
#import <Cocoa/Cocoa.h>
#interface TableViewDropper : NSTableView
#end
TableViewDropper.m
#import "TableViewDropper.h"
#implementation TableViewDropper {
BOOL highlight;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code here.
NSLog(#"init in initWithCoder in TableViewDropper.h");
[self registerForDraggedTypes:#[NSFilenamesPboardType]];
}
return self;
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
NSLog(#"performDragOperation in TableViewDropper.h");
return YES;
}
- (BOOL)prepareForDragOperation:(id)sender {
NSLog(#"prepareForDragOperation called in TableViewDropper.h");
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"%#",filenames);
return YES;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
highlight=YES;
[self setNeedsDisplay: YES];
NSLog(#"drag entered in TableViewDropper.h");
return NSDragOperationCopy;
}
- (void)draggingExited:(id)sender
{
highlight=NO;
[self setNeedsDisplay: YES];
NSLog(#"drag exit in TableViewDropper.h");
}
-(void)drawRect:(NSRect)rect
{
[super drawRect:rect];
if ( highlight ) {
//highlight by overlaying a gray border
[[NSColor greenColor] set];
[NSBezierPath setDefaultLineWidth: 18];
[NSBezierPath strokeRect: rect];
}
}
#end
The draggingEntered and draggingExited methods both get called but prepareForDragOperation and performDragOperation don't. I don't understand why not?
Next I thought I'll subclass the ClipView of the NSTableView instead. So using the same code as above and just chaging the class type in the header file to NSClipView I find that prepareForDragOperation and performDragOperation now work as expected, however the ClipView doesn't highlight.
If I subclass the NSScrollView then all the methods get called and the highlighting works but not as required. It's very thin and as expected round the entire NSTableView and not just the bit below the table header as I'd like.
So my question is what is the right thing to sublclass and what methods do I need so that when I peform a drag and drop from the Finder, the ClipView highlights properly and prepareForDragOperation and performDragOperation get called.
And also when performDragOperation is successful how can this method call a method within my FileListController telling it to create a new File object and adding it to the NSMutableArray?
Answering my own question.
It seems that subclassing the NSTableView (not the NSScrollView or the NSClipView) is the right way to go.
Including this method in the subclass :
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
return [self draggingEntered:sender];
}
Solves the problem of prepareForDragOperation and performDragOperation not being called.
To allow you to call a method within a controller class, you make the delagate of your NSTextView to be the controller. In this case FileListController.
Then within performDragOperation in the NSTableView subclass you use something like :
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
id delegate = [self delegate];
if ([delegate respondsToSelector:#selector(doSomething:)]) {
[delegate performSelector:#selector(doSomething:)
withObject:filenames];
}
This will call the doSomething method in the controller object.
Updated example project code here.

draggingEntered not firing

I have a subclass of NBox that I would like to drag around a canvas called dragBox. I don't understand why draggingEntered isn't being fired on the following code. I get a nice slideback image, but none of the destination delegates are getting fired. Why?
-(void) awakeFromNib
{
[[self superview] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
}
-(void) mouseDown:(NSEvent *)theEvent
{
[self dragImage:[[NSImage alloc] initWithContentsOfFile:#"/Users/bruce/Desktop/Untitled-1.png"] at:NSMakePoint(32, 32) offset:NSMakeSize(0,0) event:theEvent pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] source:self slideBack:YES];
}
-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender // validate
{
NSLog(#"Updated");
return [sender draggingSourceOperationMask];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"Drag Entered");
return [sender draggingSourceOperationMask];
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSLog(#"Move Box");
[self setFrameOrigin:[sender draggingLocation]];
return YES;
}
-(BOOL) prepareForDragOperation:(id<NSDraggingInfo>)sender
{NSLog(#"Prepared");
return YES;
}
In your mouseDown method you are not putting anything in the pasteboard before you initiate the drag operation. The documentation for NSView states you need to add your data on the pasteboard before sending it to that message.
Whatever destination views you have are, or should be, registering for a certain drag type. If your pasteboard does not have any matching data for that type, the destinations will not fire any of the NSDraggingProtocol messages.
Solved!
I was using the NSBox as both a destination and source. The events weren't being fired when this was the case. I moved registerDragTypes to the superview, the canvas, and implemented the draggingEntered and performDrag there. It works now...
Bruce

Notification when content changes in NSOutlineView subclass

I'd like to subclass NSOutlineView to show a label in the middle of itself when there is no content yet. Much like the inspectors in XCode:
Obviously I can't use the delegate methods, because I'm implementing this as a subclass and I have to be able to set the delegate to something else when using this class.
I didn't find any notifications that I could observe, except changes in the bounds property, but that's not very reliable.
I ended up overriding several methods of NSOutlineView to inject my code. It's not a very elegant solution, but it works. If anyone has a better solution, let me know and I might accept your answer instead.
- (void)updateEmptyLabelVisibility {
int r = [[self dataSource] outlineView:self numberOfChildrenOfItem:nil];
BOOL hasRows = r > 0;
_emptyLabel.hidden = hasRows;
}
- (void)reloadItem:(id)item {
[super reloadItem:item];
[self updateEmptyLabelVisibility];
}
- (void)reloadData {
[super reloadData];
[self updateEmptyLabelVisibility];
}
- (void)awakeFromNib {
[super awakeFromNib];
[self updateEmptyLabelVisibility];
}
- (void)endUpdates {
[super endUpdates];
[self updateEmptyLabelVisibility];
}

How to determine that user finished vertical scrolling of tableview in objective c

I want to do some things when user finished vertical scrolling of table view. Does any body know how to determine that period?
You need your view controller to become a delegate of UIScrollView: UIScrollViewDelegate
In your delegate you can implement the following methods to help determine the end state:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView{
}
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
}
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
}
There are also two properties of UIScrollView that can help:
scrollView.isDragging
scrollView.isDecelerating
Just note that there are several end 'possibilities' for the scroll view. If there is no deceleration, scrollViewDidEndDecelerating won't be called, only scrollViewDidEndDragging. However, if there is deceleration, both will be called. You can use the decelerate var in scrollViewDidEndDragging to help determine when to execute your code. For this reason, it's usually a good idea to have a separate method that's called by these delegate methods.
#implementation YourViewController
{
BOOL shouldDoYourTask;
}
-(void)performYourTask{
//Do Your Stuff
shouldDoYourTask = YES;
}
#pragma mark - ScrollView Delegate
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate
{
if (!decelerate && shouldDoYourTask)
{
[self performYourTask];
shouldDoYourTask = NO;
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (shouldDoYourTask) {
[self performYourTask];
shouldDoYourTask = NO;
}
}

Help With Intra-Application Drag and Drop -- Cocoa

Okay, I've got a custom class called "Task", which represents a task to be done. I've got an NSMatrix which acts as a calendar. I want the user to be able to drag an icon from an nscollectionview (I've had no trouble setting up the nscollectionview) onto a cell in the nsmatrix, thereby assigning that task to that day. I just can't seem to get the nsmatrix to respond to the drag or the drop at all.
I've implemented the method:
- (BOOL)collectionView:(NSCollectionView *)cv writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard
{
[pasteboard declareTypes:[NSArray arrayWithObject:TASK_UTI] owner:self];
NSUInteger index=[indexes firstIndex];
Task* task=[[cv content] objectAtIndex:index];
NSData* taskData=[NSKeyedArchiver archivedDataWithRootObject:task];
[taskData retain];
BOOL success=[pasteboard setData:taskData forType:TASK_UTI];
return success;
}
in my nscollectionview delegate as shown above.
I've sent [self registerForDraggedTypes:[NSArray arrayWithObjects:TASK_UTI, nil]] in my NSMatrix subclass (called "Calendar").
I've implemented the methods:
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
in my Calendar (NSMatrix subclass) class.
Some debugging shows that the NSMatrix/Calendar object is not even running the dragging methods above. What gives?
Did you define your Calendar class to implement dragging destination protocol?
First off, you should use your own domain name, not “com.yourcompany”, in the UTI.
Second, did you export the UTI in your Info.plist?