从新的Photos.app中拖动图片时,作为拖动信息的一部分,粘贴板中不会传递任何URL。我的应用已正确处理从例如iPhoto,Photo Booth,Aperture,...
我尝试从Photos.app中拖动图片:Finder或Pages处理正确,但不是TextEdit或预览。 Photos.app与存储在其库中的图片的工作方式似乎有所不同。
答案 0 :(得分:12)
在深入了解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;
}
}
});
}
答案 1 :(得分:3)
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
})
}