iOS 8 Action Extension からカスタム URL スキームを使ってアプリを起動する方法

# 要約

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](http://stackoverflow.com/questions/24297273/openurl-not-work-in-action-extension)）。

# JavaScript 経由で location.href を操作する

そこで、Extension から、表示しているウェブページを編集するために用意されている JavaScript を利用します。この JavaScript は、Extension が起動すると表示しているウェブページにインジェクションされ、実行されます。詳細な導入方法については、[App Extensions プログラミングガイド](https://developer.apple.com/jp/devcenter/ios/library/documentation/ExtensibilityPG.pdf) が参考になります。この JavaScript から、location.href を操作してしまおう、というわけです。

Action Extension をターゲットとしてプロジェクトに追加すると、テンプレートとして Action.js と ActionRequestHandler.[hm] が生成されます。不要な部分を取り除いて、以下のようになりました。ActionRequestHander.m の方は、もう少しシンプルにできそうですが、とりあえず動くところまで。

```js:Action.js
var Action = function() {};

Action.prototype = {
    
    run: function(arguments) {
        arguments.completionFunction()
    },
    
    finalize: function(arguments) {
        location.href = "scheme://"; // アプリの URL scheme
    }
};
    
var ExtensionPreprocessingJS = new Action
```

```objc:ActionRequestHander.m
@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" }` を渡しています。

# 動作例

[動作させるとこんな感じ](https://www.youtube.com/watch?v=OxXSA_Li31U&feature=youtu.be)です。これは、[Web MIDI Browser](http://www.taktech.org/takm/WebMIDIBrowser/Web_MIDI_Browser.html) という拙作アプリの 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 が呼び出されません。