要約
UI を持たない Action Extension から、表示されているウェブページの内容を編集するために用意されている JavaScript を経由して、アプリを起動する方法を説明しています。
Today Extension 以外からは、openURL: を使えない
Extension からは UIApplication のインスタンスを取得できませんから、
[[NSApplication sharedApplication] openURL:]
は使えません。また、Action Extension では、Extension 起動時に渡される NSExtensionContext のインスタンスメソッド
- (void)openURL:(NSURL *)URL completionHandler:(void (^)(BOOL success))completionHandler
も許可されていません。このメソッドが使えるのは、Today Extension に限られているようです(→参考: stackoverflow: openURL not work in Action Extension)。
JavaScript 経由で location.href を操作する
そこで、Extension から、表示しているウェブページを編集するために用意されている JavaScript を利用します。この JavaScript は、Extension が起動すると表示しているウェブページにインジェクションされ、実行されます。詳細な導入方法については、App Extensions プログラミングガイド が参考になります。この JavaScript から、location.href を操作してしまおう、というわけです。
Action Extension をターゲットとしてプロジェクトに追加すると、テンプレートとして Action.js と ActionRequestHandler.[hm] が生成されます。不要な部分を取り除いて、以下のようになりました。ActionRequestHander.m の方は、もう少しシンプルにできそうですが、とりあえず動くところまで。
var Action = function() {};
Action.prototype = {
run: function(arguments) {
arguments.completionFunction()
},
finalize: function(arguments) {
location.href = "scheme://"; // アプリの URL scheme
}
};
var ExtensionPreprocessingJS = new Action
@implementation ActionRequestHandler
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context
{
BOOL found = NO;
for (NSExtensionItem *item in context.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *dictionary, NSError *error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSDictionary *resultsDictionary = @{ NSExtensionJavaScriptFinalizeArgumentKey: @{ @"dummyKey" : @"dummyItem" } };
NSItemProvider *resultsProvider = [[NSItemProvider alloc] initWithItem:resultsDictionary typeIdentifier:(NSString *)kUTTypePropertyList];
NSExtensionItem *resultsItem = [[NSExtensionItem alloc] init];
resultsItem.attachments = @[ resultsProvider ];
[context completeRequestReturningItems:@[ resultsItem ] completionHandler:nil];
}];
}];
found = YES;
}
break;
}
if (found) {
break;
}
}
if (!found) {
[context completeRequestReturningItems:@[] completionHandler:nil];
}
}
@end
ホストアプリケーションから Action Extension を起動すると、ExtensionPreprocessingJS の run() が実行された後、ActionRequestHandler の - (void)beginRequestWithExtensionContext:(NSExtensionContext *)context
が呼ばれます。NSItemProvider を介してホストアプリケーションから URL を受け取った後、
[context completeRequestReturningItems:@[ resultsItem ] completionHandler:nil];
で、resultsItem を ExtensionPreprocessingJS の finalize() に渡すことができます。今回のサンプルでは、finalize() にデータを渡す必要はなかったのですが、何かしらデータを渡さないと、finalize() が実行されないようなので、このサンプルでは、@{ @"dummyKey" : @"dummyItem" }
を渡しています。
動作例
動作させるとこんな感じです。これは、Web MIDI Browser という拙作アプリの Extension として実装した例になります。Extension を経由して、Safari で開いているウェブページを、Web MIDI Browser でそのまま開くことができます。
注記: Action Extension のテンプレートを Swift で生成すると、動作しない
Xcode 6.1.1, Xcode 6.2 beta 4 いずれも、Language に Swift を選んで生成した Action Extension のテンプレートがうまく動かないようです。具体的には、NSItemProvider の loadItemForTypeIdentifier:options:completionHandler:
で渡した completionHandler が呼び出されません。