Dragging URLs into my app - cocoa

I posted this question about dragging content from OS X Finder into an NSTableView. This all works nicely now. However, if I want to drag URLs from a browser address bar into my app, I first need to drag them to the desktop (where they appear as a .webloc file) and then drag them into my app.
Is there a way to directly drag them from the browser address bar into my app, without having to drag them to the desktop first?
I tried registering kUTTypeURL but this doesn't seem to work as dragged URLs bounce back to their origin:
[[self sourcesTableView] registerForDraggedTypes:[NSArray arrayWithObjects: (NSString*)kUTTypeFileURL, (NSString*)kUTTypeURL, nil]];

In my accepted answer to your other question, the code I provided specifically restricts the URLs that your app can accept to file URLs:
NSArray* urls = [pb readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]]
options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey: NSPasteboardURLReadingFileURLsOnlyKey]];
Note the options dictionary containing a boolean YES for the NSPasteboardURLReadingFileURLsOnlyKey.
If you want to accept any URL, just do this:
NSArray* urls = [pb readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]]
options:nil];
Or even better, you can require that you'll accept any URL as long as it is of a particular type, in this case an image:
NSArray* acceptedTypes = [NSArray arrayWithObject:(NSString*)kUTTypeImage];
NSArray* urls = [pb readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]]
options:[NSDictionary dictionaryWithObject:acceptedTypes
forKey:NSPasteboardURLReadingContentsConformToTypesKey]];

Related

MacOS sandboxed application: access files without NSOpenPanel

In a sandboxed NSDocument-based application, any compatible document can be accessed using the NSOpenPanel, no matter where the document is saved. Without NSOpenPanel, the application can only access files in the sandbox container.
As my application manages two types of subclassed NSdocument (Text as a reader/writer and Image as a reader only), I try to implement a separate "Open Recent" menu for images. I disabled the the ordinary behaviour for them as they are opened by the user, overriding the noteNewRecentDocumentURL: (NSURL *)url method of the NSDocumentController to return NO for image urls. So that only the text documents appear in the ordinary File -> Open Recent menu (and open normally when user select them). Images are listed in a custom menu.
The problem occurs with these image urls, because the application is sandboxed: the application cannot open directly any image file listed in the dedicated menu (any reading operation returns a -54 error. This behaviour can be checked using:
[[NSFileManager defaultManager] isReadableFileAtPath:[fileURL path]]
which always returns FALSE in this situation. There is only one exception to that: when I reopen, from the dedicated Open Recent menu, a file that has been previously opened with the NSOpenPanel in the same application session, then closed: in this case isReadableFileAtPath: returns TRUE and the file can be accessed. But when application quits and restarts, recent images files cannot be accessed this way.
I identified thre solutions to deal with this problem:
Moving the image file in the sandbox container as soon as it has been accessed "legally" by the user, through the NSOpenPanel. It works, of course, but prevent the user from deciding on his own the location of his files! In the same way, duplicating the file in the sandbox is not a solution.
Creating an alias to these files in the sandbox. As I couldn't find a way to do this, I couldn't test whether this is a solution or not.
Disable the application sandboxing. But this is the worse solution as there are many reasons to use sandboxing!
Is there a 4th solution, which would authorize a read-only access to any image file, wherever it is located, without disabling the sandbox?
You can't access any file no matter what.
Also I am not sure what your second solution means, that is probably the reason you couldn't follow it. You probably wanted to refer to 'security-scoped bookmarks' and not to 'aliases' and they work very well and that is the path that you should follow.
Well Ivan's suggestion was excellent. After a few readings (less than an hour), I could implement those security-scoped bookmark. For interested people, here are the main findings.
add the feature to your sandboxed application's entitlement file
set the com.apple.security.files.bookmarks.document-scope (or the com.apple.security.files.bookmarks.app-scope, or both) key to TRUE.
Modify your document opening method (which calls the NSOpenPanel) like this:
-(void) openMyDocument:(id)sender{
// ... do your stuff
[self.panel beginWithCompletionHandler:^(NSInteger result) {
if (result == NSModalResponseOK) {
NSURL* selectedURL = [[self.panel URLs] objectAtIndex:0];
NSData *bookmark = nil;
NSError *error = nil;
bookmark = [selectedURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil // Make it app-scoped
error:&error];
if (error) {
NSLog(#"Error while creating bookmark for URL (%#): %#", selectedURL, error);
}
NSString *access = [NSString stringWithFormat:#"%#%#", #"Access:", [selectedURL path]];
[[NSUserDefaults standardUserDefaults] setObject:bookmark forKey:access];
[[NSUserDefaults standardUserDefaults] synchronize];
// ... then open the document your way
}
}
}
Modify the method you created to read the file without using NSOpenPanel
- (void) openDocumentForScopedURL: (NSURL *) fileURL
NSString *accessKey = [NSString stringWithFormat:#"%#%#", #"Access:", [fileURL path]];
NSData *bookmarkData = [[NSUserDefaults standardUserDefaults] objectForKey:accessKey];
NSURL *bookmarkFileURL = nil;
if (bookmarkData == nil){
// no secured-scoped bookmark found, alert the user
return;
} else {
NSError *error = nil;
BOOL bookmarkDataIsStale;
bookmarkFileURL = [NSURL
URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:nil
bookmarkDataIsStale:&bookmarkDataIsStale
error:&error];
[bookmarkFileURL startAccessingSecurityScopedResource];
}
// ... Then open your file, using bookmarkFileURL
// ... and do your stuff
// IMPORTANT. You must notify that stopped to access
[bookmarkFileURL stopAccessingSecurityScopedResource];
}

Sandbox not extended when dragging URL from Finder

I'm implementing drag and drop from the Finder into a NSTableView.
It works fine, except that I can not (always) access the referenced file.
The (test) validation method is as follows:
-(NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation {
NSArray *newURLs = [info.draggingPasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]]
options:#{NSPasteboardURLReadingFileURLsOnlyKey:#YES}];
for (NSURL *url in newURLs) {
if (![url startAccessingSecurityScopedResource])
NSLog(#"failed");
[url stopAccessingSecurityScopedResource];
}
}
This logs failed for the URLs received.
I receive the (file reference) URLs just fine from the Finder (their path is what I expect it to be).
The documentation states that dragging NSURL objects should work, so I'm a bit surprised that this doesn't work.
What's the correct way to drag URLs from the Finder to my app?

Using a URL to share an image with iOS 6 and UIActivityViewController

I am using iOS 6's UIActivityViewController.
I would like to share an image that is not available locally on my iPhone, but that it is available on a remote URL.
NSString *textToShare = _eventoTitle;
UIImage *imageToShare = [UIImage imageNamed:_iconUrl];
NSURL *url = [NSURL URLWithString:_permalink];
NSArray *activityItems = [[NSArray alloc] initWithObjects:textToShare, imageToShare, url, nil];
Unfortunately, this is not working. What am I doing wrong?
I have also tried to use the AFNetworking library:
UIImage *imageToShare = [UIImage alloc];
[*imageToShare setImageWithURL:[NSURL URLWithString:_iconUrl]];
This is not working too.
_iconUrl is something like http://www.mysite.com/picture.png
Thank you, Francesco
Try with:
UIImage *imageToShare = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", _iconUrl]]]];
Matteo
To use remote images, I implemented a UIActivityItemProvider subclass that downloads the image when requested by the UIActivityViewController. UIActivityViewController calls your UIActivityItemProvider on a background thread so at least it doesn't block the main UI. I use a synchronous call just like Matteo suggests inside my UIActivityItemProvider. However, it's really still not a great solution because it just delays when you have to go do the expensive download. UIActivityViewController doesn't request your data until the user picks one of the activity icons in the view controller. At that time, it calls the UIActivityItemProvider to get the data. So you get a delay at this time.

NSImage → NSURL → Custom Object

My application is trying to create custom objects from NSImage objects (coming from the pasteboard) but my current process only takes in image URLs.
I'd like to avoid major changes at this point so I was wondering if there was any way to get the URL of an NSImage (it seems like a reasonable expectation since one can initialize an NSImage from a URL)
Thanks.
EDIT (answer)
I went a slightly different route. Instead of getting the content of the pasteboard as an array of NSImage, I simply got it as an array of NSURL. I can then feed those into my process.
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *classArray = [NSArray arrayWithObject:[NSURL class]];
NSDictionary *options = [NSDictionary dictionary];
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
if (ok) {
NSArray *URLs = [pasteboard readObjectsForClasses:classArray options:options];
}
Quote by BlazingFrog:
(it seems like a reasonable expectation since one can initialize an NSImage from a URL)
Lets say I initialize a NSString by using:
NSString * theString = [NSString initWithContentsOfURL: encoding: error: ];
I'm sure it's not possible to retrieve the original NSURL from the NSString.
And I'm quite sure the same applies to NSImage. (Actually, completely sure.)
Indeed NSImage can be initialized by initWithContentsOfURL:.
But it can also be initialized by initWithData: or initWithPasteboard:.
The NSURL is no strict requirement for initializing a NSImage.
In other words, the NSImage might be initialized without using a URL.
The NSImage is simply a container for image representations.
Quote by Apple:
An NSImage object manages a group of image representations.
Solutions
Change you 'process' to accept NSImage.
Write the NSImage to a temporary file and use that file path.
If the image is being delivered via the standard pasteboard (i.e. the copy/paste mechanism) then there is no way to refer to it by URL because it might not have one. For instance, if you open a document in Word or Pages, select an image and copy it there is no possible way to create a URL reference to that image. It's on the pasteboard but not in the file system in a form you can access.
I think that you're going to have to modify your code to handle NSImage objects directly.

How do I handle multiple file drag/drop from Finder in Mac OS X 10.5?

I need to get the URLs of all files dragged/dropped into my application from Finder.
I have a Cocoa app running on 10.6 which does this by using the new 10.6 NSPasteboard APIs which handle multiple items on the pasteboard. I'm trying to backport this app to 10.5. How do I handle this on 10.5?
If I do something like below, I only get the first URL:
NSArray *pasteTypes = [NSArray arrayWithObjects: NSURLPboardType, nil];
NSString *bestType = [pboard availableTypeFromArray:pasteTypes];
if (bestType != nil) {
NSURL *url = [NSURL URLFromPasteboard:pboard];
}
Getting multiple filenames is easy: (While getting multiple URLs is not with 10.5)
Register your view for
NSFilenamesPboardType
In performDragOperation: do the following to get an array of file paths:
NSPasteboard* pboard = [sender draggingPasteboard];
NSArray* filenames = [pboard propertyListForType:NSFilenamesPboardType];
The IKImageKit programming topics outline a way to do this like so (paraphrased):
NSData *data = [pasteboard dataForType:NSFilenamesPboardType];
NSArray *filenames = [NSPropertyListSerialization
propertyListFromData:data
mutabilityOption:kCFPropertyListImmutable
format:nil
errorDescription:&errorDescription];
See here: Image Kit Programming Guide: Supporting Drag and Drop
The NSURLPboardType just handles one URL.
To get a list of files you need to create a NSArray from a NSFilenamesPboardType.
Apple's docs on drag and drop are pretty good, even if it's older stuff.
How do I handle [multiple items on a pasteboard] on 10.5?
Try the Pasteboard Manager.
The tricky part is that you're handling a drop, which means you're receiving an NSPasteboard already created for you, and there's no way to convert between NSPasteboard objects and PasteboardRefs. You'll have to ask the NSPasteboard for its name, then pass the same name to PasteboardCreate, and that may not work.
my two cents for swift 5.1 (drop in NSView... to be customized)
see at:
Swift: Opening a file by drag-and-drop in window

Resources