24/7 twenty-four seven

iOS/OS X application programing topics.

UIDocumentInteractionController で特定のファイルを開くことのできるアプリケーションを調べたり、直接開いたりする。

南東京iPhone開発者勉強会 #5 - Togetterまとめ
UIDocumentInteractionController
UIDocumentInteractionController で、特定の拡張子のファイルを開けるアプリがあるかどうかを調べる方法 - iOS プログラミングメモ - iPhoneアプリ開発グループ


昨日、南東京iPhone開発者勉強会 5回目で UIDocumentInteractionController について話してきました。
UIDocumentInteractionController は下記のようにあるファイルをサポートしている(開くことのできる)アプリケーションがデバイスにインストールされているとき、そのファイルを別のアプリケーションで開くというアプリケーション連携を実現するためのクラスです。
Safari で PDF ファイルを開いたときに別のアプリケーションで開くメニューが表示されたりするので見たことがあるかもしれません。


そのとき、その機能で特定のファイルについて開けるアプリがあるかどうかだけ調べたりとかできないかという話がでたので調べてみました。


まあ、そのときの結論としては UIDocumentInteractionController はプレビューするか、そのファイルを開けるアプリがある場合に選択メニューを出すかで基本的にお任せで使うものなので正攻法では無理ということなのですが、選択メニューが出せてる以上、内部的にはそういう仕組みはきっと普通にあるだろうということで調べてみました。


とりあえず、class-dump してみます。

UIDocumentInteractionController-Private.h
UIDocumentInteractionController-QLPreviewController.h
UIDocumentInteractionController-QLPreviewControllerDataSource.h
UIDocumentInteractionController-UIActionSheetDelegate.h
UIDocumentInteractionController-UIPopoverControllerDelegate.h
UIDocumentInteractionController.h


UIDocumentInteractionController-Private.h といういかにも怪しそうなファイルがあるので見てみましょう。

#import <UIKit/UIDocumentInteractionController.h>

@class UIPopoverController, _UIPreviewItemProxy;

@interface UIDocumentInteractionController (Private)
@property(readonly, nonatomic) id previewController;
@property(readonly, nonatomic) UIPopoverController *popoverController;
@property(readonly, nonatomic) _UIPreviewItemProxy *previewItemProxy;
- (void)_presentPreview:(id)arg1;
- (void)_presentOpenIn:(id)arg1;
- (void)_presentOptionsMenu:(id)arg1;
- (id)_applications:(BOOL)arg1;
- (void)_openDocumentWithApplication:(id)arg1;
- (void)_openDocumentWithCurrentApplication;
- (void)openResourceOperation:(id)arg1 didFinishCopyingResource:(id)arg2;
- (void)_finishedCopyingResource;
- (BOOL)_setupForOptionsMenu;
- (BOOL)_setupForOpenInMenu;
- (BOOL)_delegateExistsAndImplementsRequiredMethods:(id *)arg1;
- (BOOL)_setupPreviewController;
- (void)_invalidate;
- (void)_interfaceOrientationWillChange:(id)arg1;
- (id)_applicationToOpen;
- (void)_setApplicationToOpen:(id)arg1;
- (void)_setUnzippedDocumentURL:(id)arg1;
- (id)_unzippedDocumentURL;
- (BOOL)_canUnzipDocument;
- (BOOL)_isFilenameValidForUnzipping:(id)arg1;
- (BOOL)_isValidURL:(id)arg1;
- (void)_unzipDocument;
- (void)_unzipDocumentToDirectory:(id)arg1;
- (void)_zipOperationCompleted;
- (void)_cleanUpUnzippedDocument;
@end


それっぽいものが見つかりましたね。

- (id)_applications:(BOOL)arg1;
- (void)_openDocumentWithApplication:(id)arg1;


引数の意味が分かりませんがきっとこのメソッドはそのファイルを開けるアプリケーションの Array が返ってくるのでしょう。
とりあえず、呼んでみました。

NSLog(@"%@", docInteractionController.UTI);
NSLog(@"%@", [docInteractionController _applications:YES]);


私の環境では結果はこのようになりました。ちなみに引数を NO に変えてみても結果は変わらなかったので引数の意味は分かりませんです。

2011-01-31 23:20:31.788 DocInteraction[1959:307] public.plain-text
2011-01-31 23:20:31.929 DocInteraction[1959:307] (
    "LSApplicationProxy: com.hogbaysoftware.PlainText",
    "LSApplicationProxy: com.evernote.iPhone.Evernote"
)
2011-01-31 23:20:32.138 DocInteraction[1959:307] public.jpeg
2011-01-31 23:20:32.223 DocInteraction[1959:307] (
    "LSApplicationProxy: com.evernote.iPhone.Evernote"
)
2011-01-31 23:20:32.259 DocInteraction[1959:307] com.adobe.pdf
2011-01-31 23:20:32.289 DocInteraction[1959:307] (
    "LSApplicationProxy: com.lexcycle.stanza",
    "LSApplicationProxy: com.yourcompany.OpenIn",
    "LSApplicationProxy: com.evernote.iPhone.Evernote",
    "LSApplicationProxy: jp.tatsumi-sys.sidebooks",
    "LSApplicationProxy: com.apple.iBooks"
)
2011-01-31 23:20:32.315 DocInteraction[1959:307] public.html
2011-01-31 23:20:32.334 DocInteraction[1959:307] (
    "LSApplicationProxy: com.evernote.iPhone.Evernote"
)


思ったとおりそのファイルをサポートしているアプリケーションのリストが返ってきています。

LSApplicationProxy は MobileCoreServices.framework に含まれるクラスですね。

#import <MobileCoreServices/LSResourceProxy.h>

@class NSArray, NSString;

@interface LSApplicationProxy : LSResourceProxy
{
    NSArray *_privateDocumentIconNames;
    LSApplicationProxy *_privateDocumentTypeOwner;
}

+ (id)applicationProxyForIdentifier:(id)arg1;
+ (id)applicationProxyForIdentifier:(id)arg1 roleIdentifier:(id)arg2;
- (id)_initWithApplicationIdentifier:(id)arg1 roleIdentifier:(id)arg2 name:(id)arg3 resourcesDirectoryURL:(id)arg4 iconFileNames:(id)arg5 iconIsPrerendered:(BOOL)arg6;
- (void)dealloc;
@property(readonly, nonatomic) NSString *applicationIdentifier;
@property(readonly, nonatomic) NSString *roleIdentifier;
- (id)resourcesDirectoryURL;
- (id)privateDocumentIconNames;
- (void)setPrivateDocumentIconNames:(id)arg1;
- (id)privateDocumentTypeOwner;
- (void)setPrivateDocumentTypeOwner:(id)arg1;
- (id)localizedName;
- (BOOL)isEqual:(id)arg1;
- (unsigned int)hash;
- (id)description;

@end


戻り値の Array から適当なオブジェクトを引数として、先のもうひとつのメソッドに呼んであげると見事そのアプリケーションをドキュメントに関連付けて起動することができました。

NSArray *applications = [docInteractionController _applications:YES];
for (id app in applications) {
    if ([[app localizedName] isEqualToString:@"Stanza"]) {
        [docInteractionController _openDocumentWithApplication:app];
        break;
    }
}


と、まあこんな感じなのですが、なにぶん Undocumented API なのでそのまま使うのは難しいですね。

ただ使えるといろいろと便利な API だと思いますので、開放してほしいと思う方はバグレポートから要望を送ってみるといいと思いますよ。

Bug Reporting - Apple Developer