Как приложение OS X может принимать перетаскивание файлов изображений из Photos.app?

При перетаскивании изображений из нового Photos.app в картотеке URL не передается в качестве части информации перетаскивания. Мое приложение уже правильно обрабатывает изображения, переданные из, например, iPhoto, Photo Booth, Aperture,...

Я попробовал перетаскивать изображения из Photos.app: Finder или Pages обрабатывают это правильно, но не TextEdit или Preview. Кажется, что-то отличается от того, как Photos.app работает с изображениями, хранящимися в его библиотеке.

Ответ 1

После перехода на NSPasteboard и перехода через приложение, я понял, что Photos.app передает "обещанные файлы" в картотеке и нашел этот поток в списке рассылки Apple с некоторыми ответами: http://prod.lists.apple.com/archives/cocoa-dev/2015/Apr/msg00448.html

Вот как я, наконец, решил это, в классе, который обрабатывает перетаскивание файлов в документ. Класс является контроллером представления, который обрабатывает обычные методы перетаскивания, потому что он в цепочке ответчиков.

Метод удобства обнаруживает, что отправитель перетаскивания имеет любой связанный с файлом контент:

- (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;
}

У меня также есть способ извлечь URL-адрес в случае, если он не является "обещанным файлом":

- (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];
}

Вот, наконец, метод, используемый для обработки капли. Это не совсем точно мой код, поскольку я упростил внутреннюю обработку перетаскивания в удобные методы, которые позволяют мне скрывать детали, специфичные для приложения. У меня также есть специальный класс для обработки событий файловой системы FileSystemEventCenter слева как упражнение для читателя. Кроме того, в представленном здесь примере я обрабатываю только перетаскивание файла один. Вам придется адаптировать эти части к вашему собственному случаю.

- (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;
             }
         }
    });
}

Ответ 2

Apple сделала это немного легче в 10.12 с NSFilePromiseReceiver. Это еще долгий процесс, но немного меньше.

Вот как я это делаю. Я действительно разделил это на расширение, но я упростил его для этого примера.

    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {

        let pasteboard: NSPasteboard = sender.draggingPasteboard()

            guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] else {
                return
            }
            var images = [NSImage]()
            var errors = [Error]()

            let filePromiseGroup = DispatchGroup()
            let operationQueue = OperationQueue()
            let newTempDirectory: URL
            do {
        let newTempDirectory = (NSTemporaryDirectory() + (UUID().uuidString) + "/") as String
        let newTempDirectoryURL = URL(fileURLWithPath: newTempDirectory, isDirectory: true)

        try FileManager.default.createDirectory(at: newTempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
            }
            catch {
                return
            }

            filePromises.forEach({ filePromiseReceiver in

                filePromiseGroup.enter()

                filePromiseReceiver.receivePromisedFiles(atDestination: newTempDirectory,
                                                         options: [:],
                                                         operationQueue: operationQueue,
                                                         reader: { (url, error) in

                                                            if let error = error {
                                                                errors.append(error)
                                                            }
                                                            else if let image = NSImage(contentsOf: url) {
                                                                images.append(image)
                                                            }
                                                            else {
                                                                errors.append(PasteboardError.noLoadableImagesFound)
                                                            }

                                                            filePromiseGroup.leave()
                })

            })

            filePromiseGroup.notify(queue: DispatchQueue.main,
                                    execute: {
// All done, check your images and errors array

            })
}