LoginSignup
25
27

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-01-18

要約

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 の方は、もう少しシンプルにできそうですが、とりあえず動くところまで。

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

Action.prototype = {

    run: function(arguments) {
        arguments.completionFunction()
    },

    finalize: function(arguments) {
        location.href = "scheme://"; // アプリの URL scheme
    }
};

var ExtensionPreprocessingJS = new Action
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" } を渡しています。

動作例

動作させるとこんな感じです。これは、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 が呼び出されません。

25
27
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
27