How do I send a file to a different App knowing which UTI the App supports? Lets say the file has no file extension, but I happen to know the UTI of the file.
I tried the following:
// target is a NSURL with the location of the extension less file on the system
// knownUTI is a NSString containing the UTI of the file
UIDocumentInteractionController* dic = [UIDocumentInteractionController interactionControllerWithURL:target];
[dic retain];
dic.delegate = self;
dic.UTI = knownUTI;
[dic presentOpenInMenuFromRect:CGRectZero inView:superController.view animated:YES]
It shows the supported App, however, if I select it, it won't switch the App. The delegate calls the
- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application
but
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
is never called and the Application is never switching.
The target App exports its UTI in the following:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Migration DocType</string>
<key>CFBundleTypeRol</key>
<string>Shell</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.mycomp.customstring</string>
</array>
</dict>
</array>
...
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>My custom UTI</string>
<key>UTTypeIdentifier</key>
<string>com.mycomp.customstring</string>
</dict>
</array>
As this did not work, I also tried adding a custom extension. Still, it would not work in this way. When adding the custom extension to the file I hand over to the DocumentInteractionController
and it works. However, the list of applications shows all other applications supporting the same file extension regardless of the UTI type I supply.
Say I declare 2 UTIs in 2 different applications:
App1 with UTI1: com.mycomp.a with extension .abc
App2 with UTI2: com.mycomp.b with extension .abc
When handing the file to the DocumentInteractionController, and setting the UTI to com.mycomp.a
it will also show App2 as a possible application being able to handle the file.
I defined a UTI with extension in the following manner:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>My UTI Type</string>
<key>UTTypeIdentifier</key>
<string>com.mycomp.a</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<string>abc</string>
<key>public.mime-type</key>
<string>application/abc</string>
</dict>
</dict>
</array>
I would really appreciate your help, I'm kind of stuck. So, again the question: How do I send a file to an App with known UTI either without extension or having the same extension as other files of which I don't want to show the applications as choice in DocumentInteractionController?
Thanks
I found a solution to this problem. However, I think it is not a very good one.
During testing I found out that when leaving the file extension away, the UIDocumentInteractionController
will show the applications depending on the UTI
I specified. When sending the file to the target application nothing would happen. I concluded that I need an file extension to do the final sending.
My approach was to modify the URL
property before the file is sent to the target application and supply it the same file but with a file extension the target application accepts. Nevertheless, my application just crashed. I profiled it with Instruments and found that the problem was due to UIDocumentInteractionController
overreleasing some proxy object.
I also saw that the final overrelease was in a method called _invalidate
of UIDocumentInteractionController(Private)
category was called which released the object.
As categories cannot be overridden by other categories I decided to swizzle the category method with my own implementation checking if the URL was containing a file extension or not and either redirect the call to the original _invalidate
method or just do nothing.
The following codes shows what I did:
#include <objc/runtime.h>
@interface UIDocumentInteractionController(InvalidationRedirect)
-(void)_invalidateMY;
+(void)load;
void Swizzle(Class c, SEL orig, SEL newSEL);
@end
@implementation UIDocumentInteractionController(InvalidationRedirect)
void Swizzle(Class c, SEL orig, SEL newSEL)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, newSEL);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
-(void)_invalidateMY{
@synchronized(self) {
if(![[[[self.URL lastPathComponent] componentsSeparatedByString:@"."] lastObject] isEqualToString:@"extension"]) {
[self _invalidateMY];
}
}
}
+(void)load
{
Swizzle([UIDocumentInteractionController class], @selector(_invalidate), @selector(_invalidateMY));
}
@end
This code exchanges the original _invalidate
method with _invalidateMY
, resulting in every call to _invalidate
calling _invalidateMY
and vice versa.
The following code shows how I handle the UIDocumentInteractionController
:
// create a file without extension
NSString *fileName = @"myFile";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL* target = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", documentsDirectory, fileName]];
if([[@"THIS IS JUST A TEST STRING" dataUsingEncoding:NSUTF8StringEncoding] writeToURL:target atomically:NO]) {
NSLog(@"file written successfully");
}else {
NSLog(@"Error.. file writing failed");
}
UIDocumentInteractionController* dic = [UIDocumentInteractionController interactionControllerWithURL:target];
[dic retain];
dic.delegate = self;
// set the UTI to the known UTI we want to list applications for
dic.UTI = @"com.mycomp.a";
[dic presentOpenInMenuFromRect:CGRectZero inView:superController.view animated:YES];
And this code shows the UIDocumentInteractionController
's delegate method which exchanges the URL:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application
{
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSError *error;
NSURL* newTarget = [NSURL URLWithString:[NSString stringWithFormat:@"%@.extension", controller.URL]];
// rename file to file with extension
if (![fileMgr moveItemAtURL:controller.URL toURL:newTarget error:&error] && error) {
NSLog(@"Error moving file: %@", [error localizedDescription]);
}
@synchronized(controller) {
//exchange URL with URL+extension
controller.URL = newTarget; //<- this results in calling _invalidate
}
NSLog(@"%@", [NSString stringWithContentsOfURL:controller.URL encoding:NSUTF8StringEncoding error:nil]);
}
This solution works, but in my opinion it is a dirty hack, there must be a better solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With