I'm working on a small app that works with image (it is not a document based app).
I want to be able to open image files by dragging them to the app icon.
I have looked at this very clear explanation
Cocoa/Obj-C - Open file when dragging it to application icon
and have added the ability to open .png files by dragging to the app icon.
Do I have to add ALL the file types I require separately , or is there a way to specify ALL image types (for instance all image types that an NSImageView will accept)?
In your application’s Info.plist’s, you can specify the Uniform Type Identifier public.image in LSItemContentTypes for a document type even if your application is not document-based:
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.image</string>
</array>
</dict>
</array>
or
If you’re opening other types of files as well, you may want to check whether the dropped file is an image:
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSError *error;
NSString *fileUTI = [[NSWorkspace sharedWorkspace] typeOfFile:filename error:&error];
if (!fileUTI)
NSLog(#"Error when trying to detect the type of file %#: %#", filename, error);
else if (UTTypeConformsTo((__bridge CFStringRef)fileUTI, kUTTypeImage))
NSLog(#"%# is an image", filename);
else
NSLog(#"%# is not an image", filename);
return YES;
}
Related
First Issue: No Sandboxing
I am having an issue with some code to copy a file. With sandboxing turned off completely and this code.
- (IBAction)installWidget:(id)sender
{
// copy widgets to users library
NSError* error = nil;
NSString *testUrl = #"~/Library/Widgets/test.wdgt";
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl]) {
NSLog(#"yes");
}
NSString *testUrl2 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: #"test.wdgt"];
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl2]) {
NSLog(#"yes");
}
[[NSFileManager defaultManager] removeItemAtPath:testUrl error:nil];
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:&error];
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
}
With sandboxing turned off I get the following error.
The file “test.wdgt” doesn’t exist. I tried zipping it thinking it was a flat file issue when changing the code from test.wdgt to test.zip I got the same error. The file is included in the resources bundle but its not letting me copy it.
I tried moving the app from outside of the build folder, same issue. I also tried cleaning the build folder same issue.
Second Issue: With Sandboxing
I get the following error with Sandboxing enabled. You don't have permission to access the directory Widgets.
Entitlement file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.assets.pictures.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/Users/jon/Library/Widgets/</string>
</array>
</dict>
</plist>
I am beating my head against the wall. I tried adding a simple image and then replacing the image name with the widget name and it copied fine as long as sandboxing was disabled. I wrote a widget thats works with my app and I want to distribute it by allowing the user to install the widget by clicking a button in the app or from a menu item. Any help is appreciated.
On the non-sandbox question, you need to expand the tilde(~) before using that in a path for file system operations. One way to do this is by calling -stringByExpandingTildeInPath on the string you have created, but the best practice for getting folders like this would be to use:
NSArray *paths = NSSearchPathForDirectoriesInDomains
( NSLibraryDirectory, NSUserDomainMask, YES);
This will return an array (should only be 1 element) containing the string to the path for the User's Library directory. Then you can add your specific path elements to that by using -stringByAppendingPathComponent:.
Thus, you'd get the full path by taking:
NSString *widgetsPath = [[paths objectAtIndex: 0] stringByAppendingPathComponent:
#"Widgets"];
As for doing in the sandbox, your current code will fail due to the wrong directory, which won't have access (since ~ is being interpreted as a path component and not to replace the User's home directory). However, you obviously can't use the absolute-path exception in shipping code by enumerating every user. Chances are you will need to find another approach to installing this if you are going to be sandboxed. You may be able to just open the widget file and thus get the OS to offer to copy it for you. Otherwise, you're going to have to ask the user's permission in some way, such as by popping up an open window and passing in the path to the user's Widget folder.
I am trying to create a new document UTI for my App so that people can share points of interest with others. From what I can understand on SO, Tutorials, and from Apple's documentation, you need to do the following:
Create a Document Type in the .plist
Create an Exported UTI that corresponds with it
Use the method: -(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
From what I understand, as long as you did those right, you should be able to open the file through Mail without any problems. Unfortunately it isn't working for my own custom UTIs. I DO see my App in the list of "Open with..." in Mail, but when I select it, my App doesn't open at all. It just dosen't do anything at all not only when the App isn't open, but when the App is open. Mail stays up and nothing happens at all. I also checked the console using "Organizer" and there is absolutely nothing that happens.
Originally I thought my plist was wrong, so I tested opening a public UTI (I added the com.adobe.pdf document type) and my app launched just fine (though it promptly crashed because I don't actually support PDFs ;)). But the point was that it launched without any problem.
The only thing I can think of that might be a problem is HOW I am creating the file. I am creating the file in an email by the use of this method (also in the App to export):
MFMailComposeViewController *picker = [[[MFMailComposeViewController alloc] init] autorelease];
[picker setSubject:[NSString stringWithFormat:#"My place: %#",POIName]];
[picker addAttachmentData:customPOIData mimeType:#"application/customPOI" fileName:[NSString stringWithFormat:#"%#.icp",POIName]];
[picker setMessageBody:#"Check out this great place I found!" isHTML:NO];
[picker setMailComposeDelegate:self];
[self presentModalViewController:picker animated:YES];
Is there anything wrong with that?
Also, here is my plist code:
CFBundleDocumentTypes:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Custom POI</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.imadev.icp</string>
</array>
</dict>
</array>
UTExportedTypeDeclarations:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Custom POI</string>
<key>UTTypeIdentifier</key>
<string>com.imadev.icp</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>icp</string>
</array>
<key>public.mime-type</key>
<string>application/customPOI</string>
</dict>
</dict>
</array>
Thanks a lot for any help!!
-Mark
I finally figured out what was wrong by pulling apart all the code.
When I change "public.filename-extension" to a string and not an array of strings it works. Don't ask me why... I think it's strange we can't use an array of file extensions. But apparently that was it.
Any ideas as to why that happened?
In my case, I could keep filename-extension as an array without problems. Instead, it was the 'public.data' that was the issue. I switched to 'public.image' and then had jpg, jpeg, and png as file extensions, and it suddenly worked.
I have an NSDocument based app that already handles *.txt files. I can drag and drop those files in the dock and my app will launch correctly.
Now I want to be able to open *.text files too. So I have added:
<key>CFBundleTypeExtensions</key>
<array>
<string>.txt</string>
<string>.text</string>
</array>
to my *plist
However I can't open *.text files by drag&dropping them in the dock. (I can do MyApp> File>Open> myFile.text )
I can open *.txt as usual, and drag and drop them in the dock.
I have tried implementing these NSApp delegate methods but now I get an error and I can't open any kind of file.
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename{
NSURL *url = [NSURL URLWithString:filename];
NSError *error = nil;
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:YES error:&error];
if (error) {
NSLog(#"error: %#", [error localizedDescription]);
error = nil;
return NO;
}
return YES;
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames{
for (NSString *filename in filenames) {
[self application:sender openFile:filename];
}
}
Error:
typeForContentsOfURL:error: must be
overridden for your application to
support non-'file:' URLs.
I am not subclassing NSDocumentController, so do I really need to? or is there a better/easier way of doing this?
Thanks in advance
The file extensions in the Info.plist file should not contain a period character (.). What happens if you do this?:
<key>CFBundleTypeExtensions</key>
<array>
<string>txt</string>
<string>text</string>
</array>
Currently there is a button on my app inteface which allow to open a file, here is my open code:
In my app.h:
- (IBAction)selectFile:(id)sender;
In my app.m:
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
}
- (IBAction)selectFile:(id)sender {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
NSArray *fileTypes = [NSArray arrayWithObjects:#"xml",nil];
NSInteger result = [openPanel runModalForDirectory:NSHomeDirectory() file:nil types:fileTypes ];
if(result == NSOKButton){
NSString * input = [openPanel filename];
How can I edit my code to allow opening with the application-icon drag & drop?
Note: I edited the .plist file and added a line for "xml" but it change anything, got an error when my file is dropped on the icon.
Note 2: I linked the "File -> Open..." to the selectFile: wich refer to my code
Note 3: My app isn't a document-based application
Thanks for your help!
Miskia
First add the proper extensions to CFBundleDocumentTypes inside the .plist file.
Next implement the following delegates:
- application:openFile: (one file dropped)
- application:openFiles: (multiple files dropped)
Reference:
NSApplicationDelegate Protocol Reference
Response to comment:
Step by step example, hopefully it makes everything clear :)
Add to the .plist file:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>xml</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>application.icns</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>text/xml</string>
</array>
<key>CFBundleTypeName</key>
<string>XML File</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSIsAppleDefaultForType</key>
<true/>
</dict>
</array>
Add to ...AppDelegate.h
- (BOOL)processFile:(NSString *)file;
- (IBAction)openFileManually:(id)sender;
Add to ...AppDelegate.m
- (IBAction)openFileManually:(id)sender;
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
NSArray *fileTypes = [NSArray arrayWithObjects:#"xml",nil];
NSInteger result = [openPanel runModalForDirectory:NSHomeDirectory() file:nil types:fileTypes ];
if(result == NSOKButton){
[self processFile:[openPanel filename]];
}
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
return [self processFile:filename];
}
- (BOOL)processFile:(NSString *)file
{
NSLog(#"The following file has been dropped or selected: %#",file);
// Process file here
return YES; // Return YES when file processed succesfull, else return NO.
}
for a quick&dirty solution:
Cocoa: Drag and Drop any file type
Tested under Xcode Version 11.4 (11E146)
catalina 10.15.4 (19E266)
I've recently become interested in writing some system services for OS X but since I have no application to advertise the services under I must resort to writing standalone system services. Apple's documentation on System Services is spartan as it is, but its documentation on standalone services is non-existant.
What I have thus far is an Xcode project built from the bundle package, with the two sources HashifyService.h and HashifyService.m. Here is the test code I have:
- (void) doServiceWork:(NSPasteboard *)pboard
userData:(NSString *)userData
error:(NSString **)error {
NSLog(#"Actually in the service now");
NSString *pboardString;
NSArray *types;
NSLog(#"do test magic service! (pboard: %#, types: %#)", pboard, [pboard types]);
NSString* outputString = #"It Worked";
types = [NSArray arrayWithObject:NSStringPboardType];
[pboard declareTypes:types owner:nil];
[pboard setString:outputString forType:NSStringPboardType];
[outputString release];
return;
}
and this is the NSServices entry in my Info.plist:
<dict>
<key>NSMenuItem</key>
<dict>
<key>Menu item title</key>
<string>HashifyTest</string>
</dict>
<key>NSMessage</key>
<string>doServiceWork</string>
<key>NSPortName</key>
<string>HashifyService</string>
<key>NSReturnTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
I then build the service bundle and place it in ~/Library/Services/ where it is appropriately detected and I am given the option to use the service. Upon activating the service, however, an error occurs and is logged to Console:
..../Hashify.service/Contents/MacOS/Hashify: cannot execute binary file
What am I doing wrong?
You need a main() function. That should register the service using NSRegisterServicesProvider() and enter the run loop. That is in the documentation.