UIDocumentInteractionController,没有文件扩展名,但 UTI

发布于 2024-12-21 05:10:03 字数 3655 浏览 2 评论 0原文

如何将文件发送到不同的应用程序并了解该应用程序支持哪种 UTI?假设该文件没有文件扩展名,但我碰巧知道该文件的 UTI。

我尝试了以下操作:

// 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]

它显示了支持的应用程序,但是,如果我选择它,它不会切换应用程序。委托调用

- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application

但从

- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application

未被调用,并且应用程序永远不会切换。

目标应用程序导出其 UTI 如下:

    <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>

由于这不起作用,我还尝试添加自定义扩展。不过,这样是行不通的。将自定义扩展添加到文件时,我将其移交给 DocumentInteractionController 并且它可以工作。但是,应用程序列表显示所有其他应用程序都支持相同的文件扩展名,无论 UTI I 型电源如何。

假设我在 2 个不同的应用程序中声明 2 个 UTI:

App1 with UTI1: com.mycomp.a  with extension .abc
App2 with UTI2: com.mycomp.b  with extension .abc

将文件传递给 DocumentInteractionController 并将 UTI 设置为 com.mycomp.a 时,它还会将 App2 显示为能够处理该文件的可能应用程序。

我通过以下方式定义了带有扩展名的 UTI:

<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>

我非常感谢您的帮助,我有点卡住了。 那么,问题又来了:如何将文件发送到具有已知 UTI 的应用程序,或者没有扩展名,或者与我不想在 DocumentInteractionController 中显示应用程序的其他文件具有相同的扩展名?

谢谢

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅此而已 2024-12-28 05:10:03

我找到了解决这个问题的方法。不过,我认为这并不是一件很好的事情。

在测试过程中,我发现当保留文件扩展名时,UIDocumentInteractionController 将根据我指定的 UTI 显示应用程序。将文件发送到目标应用程序时不会发生任何事情。我的结论是我需要一个文件扩展名来完成最终的发送。

我的方法是在将文件发送到目标应用程序之前修改 URL 属性,并向其提供相同的文件,但具有目标应用程序接受的文件扩展名。尽管如此,我的应用程序还是崩溃了。我使用 Instruments 对其进行了分析,发现问题是由于 UIDocumentInteractionController 过度释放了某些代理对象。
我还看到最终的过度释放是在 UIDocumentInteractionController(Private) 类别的名为 _invalidate 的方法中调用的,该方法释放了对象。

由于类别不能被其他类别覆盖,我决定用我自己的实现来混合类别方法,检查 URL 是否包含文件扩展名,然后将调用重定向到原始 _invalidate 方法,或者只是执行没有什么。

以下代码显示了我所做的操作:

#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

此代码将原始 _invalidate 方法与 _invalidateMY 交换,导致每次调用 _invalidate 时都会调用 _invalidateMY 反之亦然。

以下代码显示了我如何处理 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];

此代码显示了 UIDocumentInteractionController 的交换 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]);
}

此解决方案有效,但在我看来,它是一个肮脏的黑客,必须有更好的解决方案。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文