Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rearranging table rows by dragging in Lion

I am having trouble using the new Lion functionality to rearrange rows in my app. I am using outlineView:pasteboardWriterForItem: to store the row indexes so that I can access them later when I validate/accept the drop. I create a new NSPasteboardItem to return, and am attempting to store the row number as so:

[pbItem setData: [NSKeyedArchiver archivedDataWithRootObject: [NSNumber numberWithInteger: [fTableView rowForItem: item]]]
                                                     forType: TABLE_VIEW_DATA_TYPE];

TABLE_VIEW_DATA_TYPE is a custom string I'm using to distinguish my custom data in the dragging pasteboard. I don't use it outside of dragging these rows.

When attempting the drag, I receive in Console: 'TableViewDataType' is not a valid UTI string. Cannot set data for an invalid UTI.

Of course I could use some of the built-in UTIs for pasteboards, but none of them apply (and using them causes the drag to accept drags other than the rows, which it shouldn't). Is there something I'm missing, like a way to define a custom UTI just for dragging (without making it a "real" UTI since I have no use for it outside of the internal dragging, so it shouldn't be public).

Thanks for any help!

like image 659
livings124 Avatar asked Dec 31 '11 02:12

livings124


3 Answers

I had similar requirements except I had a grid of objects that I wanted to rearrange by dragging selected objects to a new location. There are several ways of doing this, including creating a custom object and implementing the NSPasteboardWriting and NSPasteboardReading protocols, (and NSCoding protocols if you will be reading data as NSPasteboardReadingAsKeyedArchive), but this seems to be overkill for dragging of objects that remain internal to the application.

What I did involves using the NSPasteboardItem as a wrapper with a custom UTI type (it already implements the NSPasteboardWriting and NSPasteboardReading protocols). First declare a custom UTI type:

#define kUTIMyCustomType @“com.mycompany.MyApp.MyCustomType”

This needs to be defined in the ‘com.domain.MyApp’ format otherwise you will get errors of the form: “XXX is not a valid UTI string. Cannot set data for an invalid UTI.” Apple mentions this in their documentation.

Then you must register this custom UTI type in the view in which your dragging will occur. This can be done at runtime, and does not require any .plist additions. In your view's init method add the following:

[self registerForDraggedTypes:[NSArray arrayWithObjects:(NSString *)kUTIMyCustomType, nil]];

Now, make sure that the delegate is set for this view, and the delegate object implements the required NSDraggingSource and NSDraggingDestination protocol methods. This will allow you to avoid breaking the MVC design pattern, by allowing the designated controller object to handle placing the data on the pasteboard which will likely involve querying model data (i.e., indexes).

Specifically, for placing on the dragging pasteboard the indexes of objects to be moved when dragging begins as NSPasteboardItem wrappers of your index data:

- (void) draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint
{
    NSPasteboard * pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
    [pboard clearContents];

    NSMutableArray * selectedIndexes = [NSMutableArray array];

    // Add NSString indexes for dragged items to pasteboard in NSPasteboardItem wrappers.
    for (MyModel * myModel in [self selectedObjects])
    {
        NSPasteboardItem * pasteboardItem = [[[NSPasteboardItem alloc] init] autorelease];
        [pasteboardItem setString:[NSString stringWithFormat:@"%@", [myModel index]]
                        forType:kUTIMyCustomType];
        [selectedIndexes addObject:pasteboardItem];
    }

    [pboard writeObjects:selectedIndexes];
}

And when the dragging operation completes, to read the dragged index NSPasteboardItem data:

- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
    NSPasteboard * pasteboard = [sender draggingPasteboard];

    // Check for custom NSPasteboardItem's which wrap our custom object indexes.
    NSArray * classArray = [NSArray arrayWithObject:[NSPasteboardItem class]];
    NSArray * items = [pasteboard readObjectsForClasses:classArray options:[NSDictionary dictionary]];

    if (items == nil)
        return NO;

    // Convert array of NSPasteboardItem's with NSString index reps. to array of NSNumber indexes.
    NSMutableArray * indexes = [NSMutableArray array];
    for (NSPasteboardItem * item in items)
        [indexes addObject:[NSNumber numberWithInteger:[[item stringForType:kUTIMyCustomType] integerValue]]];

    //
    // Handle dragged indexes…
    //

    return YES;
}
like image 123
Dalmazio Avatar answered Oct 19 '22 17:10

Dalmazio


Another technique you can use is to just store the indices of the objects you're dragging in an instance variable on the side. Putting everything on the pasteboard isn't strictly necessary unless you're accepting items from another app or vice versa.

  1. In awakeFromNib, register for the NSStringPboardType.
  2. In …pasteboardWriterForRow, return [NSString string].
  3. In …draggingSession:willBegin…, set your instance variable to the indices you want to track.
  4. In validateDrop, return NSDragOperationNone if your instance variable is nil or the view is not yours.
  5. In …draggingSession:ended…, nil out your instance variable.

Hope that helps… I'm using the technique for a table view, but it should be virtually identical for an outline view.

like image 6
Ryan Avatar answered Oct 19 '22 17:10

Ryan


Instead of using a vanilla NSPasteboardItem, you should create a custom object that conforms to the NSPasteboardWriting protocol.

In your custom object, you can implement writableTypesForPasteboard: to return a list of custom UTIs that your pasteboard item supports. You then implement pasteboardPropertyListForType: to return a plist representation of your object for the appropriate custom UTI when the pasteboard asks for it.

You can create a plist from arbitrary data using the +propertyListWithData:options:format:error: method of NSPropertyListSerialization.

You would then override tableView:pasteboardWriterForRow: in your table view data source to return an instance of your custom object.

like image 3
Rob Keniger Avatar answered Oct 19 '22 18:10

Rob Keniger