Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
388 views
in Technique[技术] by (71.8m points)

objective c - How can my OS X app accept drag-and-drop of picture files from Photos.app?

When dragging pictures from the new Photos.app, no URL is passed in the pasteboard as part of the dragging info. My app already correctly handles images passed from e.g. iPhoto, Photo Booth, Aperture,...

I tried dragging pictures from Photos.app: Finder or Pages handle that properly, but not TextEdit or Preview. There seems to be something different about the way Photos.app works with the pictures stored in its library.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

After digging into NSPasteboard and stepping through the app, I realized Photos.app is passing "promised files" in the pasteboard, and found this thread in an Apple's mailing list with some answers: http://prod.lists.apple.com/archives/cocoa-dev/2015/Apr/msg00448.html

Here is how I finally solved it, in the class that handles drag and drop of files into a document. The class is a view controller that handles the usual drag/drop methods because it's in the responder chain.

A convenience method detects wether the sender of a drag has any file-related content:

- (BOOL)hasFileURLOrPromisedFileURLWithDraggingInfo:(id <NSDraggingInfo>)sender
{
    NSArray *relevantTypes = @[@"com.apple.pasteboard.promised-file-url", @"public.file-url"];
    for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems])
    {
        if ([item availableTypeFromArray:relevantTypes] != nil)
        {
            return YES;
        }
    }
    return NO;
}

I also have a method to extract the URL in the case where it's not a "promised file":

- (NSURL *)fileURLWithDraggingInfo:(id <NSDraggingInfo>)sender
{
    NSPasteboard *pasteboard = [sender draggingPasteboard];
    NSDictionary *options = [NSDictionary dictionaryWithObject:@YES forKey:NSPasteboardURLReadingFileURLsOnlyKey];
    NSArray *results = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
    return [results lastObject];
}

Here is finally the method used to handle a drop. It's not quite exactly my code, as I simplified the internal handling of dragging into convenience methods that allow me to hide the parts specific for the app. I also have a special class for handling file system events FileSystemEventCenter left as an exercise to the reader. Also, in the case presented here, I only handle dragging one file. You'll have to adapt those parts to your own case.

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
    if ([self hasFileURLOrPromisedFileURLWithDraggingInfo:sender])
    {
        [self updateAppearanceWithDraggingInfo:sender];
        return NSDragOperationCopy;
    }
    else
    {
        return NSDragOperationNone;
    }
}

- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
    return [self draggingEntered:sender];
}

- (void)draggingExited:(id <NSDraggingInfo>)sender
{
    [self updateAppearanceWithDraggingInfo:nil];
}

- (void)draggingEnded:(id <NSDraggingInfo>)sender
{
    [self updateAppearanceWithDraggingInfo:nil];
}

- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
    return [self hasFileURLOrPromisedFileURLWithDraggingInfo:sender];
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
    // promised URL
    NSPasteboard *pasteboard = [sender draggingPasteboard];
    if ([[pasteboard types] containsObject:NSFilesPromisePboardType])
    {
        // promised files have to be created in a specific directory
        NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
        if ([[NSFileManager defaultManager] createDirectoryAtPath:tempPath withIntermediateDirectories:NO attributes:nil error:NULL] == NO)
        {
            return NO;
        }

        // the files will be created later: we keep an eye on that using filesystem events
        // `FileSystemEventCenter` is a wrapper around FSEvent
        NSArray *filenames = [sender namesOfPromisedFilesDroppedAtDestination:[NSURL fileURLWithPath:tempPath]];
        DLog(@"file names: %@", filenames);
        if (filenames.count > 0)
        {
            self.promisedFileNames = filenames;
            self.directoryForPromisedFiles = tempPath.stringByStandardizingPath;
            self.targetForPromisedFiles = [self dropTargetForDraggingInfo:sender];
            [[FileSystemEventCenter defaultCenter] addObserver:self selector:@selector(promisedFilesUpdated:) path:tempPath];
            return YES;
        }
        else
        {
            return NO;
        }
    }

    // URL already here
    NSURL *fileURL = [self fileURLWithDraggingInfo:sender];
    if (fileURL)
    {
        [self insertURL:fileURL target:[self dropTargetForDraggingInfo:sender]];
        return YES;
    }
    else
    {
        return NO;
    }
}

- (void)promisedFilesUpdated:(FDFileSystemEvent *)event
{
    dispatch_async(dispatch_get_main_queue(),^
     {
         if (self.directoryForPromisedFiles == nil)
         {
             return;
         }

         NSString *eventPath = event.path.stringByStandardizingPath;
         if ([eventPath hasSuffix:self.directoryForPromisedFiles] == NO)
         {
             [[FileSystemEventCenter defaultCenter] removeObserver:self path:self.directoryForPromisedFiles];
             self.directoryForPromisedFiles = nil;
             self.promisedFileNames = nil;
             self.targetForPromisedFiles = nil;
             return;
         }

         for (NSString *fileName in self.promisedFileNames)
         {
             NSURL *fileURL = [NSURL fileURLWithPath:[self.directoryForPromisedFiles stringByAppendingPathComponent:fileName]];
             if ([[NSFileManager defaultManager] fileExistsAtPath:fileURL.path])
             {
                 [self insertURL:fileURL target:[self dropTargetForDraggingInfo:sender]];
                 [[FileSystemEventCenter defaultCenter] removeObserver:self path:self.directoryForPromisedFiles];
                 self.directoryForPromisedFiles = nil;
                 self.promisedFileNames = nil;
                 self.targetForPromisedFiles = nil;
                 return;
             }
         }
    });
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...