web-dev-qa-db-ja.com

openURLがAction Extensionで機能しない

次のコードを追加します。

- (IBAction)done {
    // Return any edited content to the Host app.
    // This template doesn't do anything, so we just echo the passed in items.

    NSURL *url = [NSURL URLWithString:@"lister://today"];
    [self.extensionContext openURL:url completionHandler:^(BOOL success) {
        NSLog(@"fun=%s after completion. success=%d", __func__, success);
    }];
    [self.extensionContext completeRequestReturningItems:self.extensionContext.inputItems completionHandler:nil];

}

アクション拡張ターゲットを作成した後。しかし、それは機能しません。

私の目的は、ユーザーがPhotos.app(iOSのデフォルトのPhotos.appまたは呼び出されたギャラリー)で写真を表示し、共有ボタンをクリックして拡張機能ビューを起動することです。 Photos.appから自分のアプリに画像を転送し、アプリで画像を処理またはアップロードできます。

「CFBundleDocumentTypes」も試してみましたが、機能しません。

任意の助けをいただければ幸いです。

44
Laurence Fan

これは仕様です。カスタムアクションをアプリランチャーにしたくない。

20
Ian Baird

これは私が以前使用していたことです:

UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"https://iTunes.Apple.com/us/app/watuu/id304697459";
NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv='refresh' content='0; URL=%@'></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:2.0];

この場合、UIInputViewControllerからこの呼び出しをインスタンス化していることに注意してください。

このメソッドは、収容アプリのURLスキームを使用しても機能するはずです

2015年4月17日更新:これはiOS 8.3では機能しません。私たちは解決策を探しており、すぐに答えを更新します

2015年6月1日更新:iOS 8.3で機能するソリューションが見つかりました

var responder = self as UIResponder?

while (responder != nil){
    if responder!.respondsToSelector(Selector("openURL:")) == true{
        responder!.callSelector(Selector("openURL:"), object: url, delay: 0)
    }
    responder = responder!.nextResponder()
}

これにより、openURLを送信する適切なレスポンダーが見つかります。

SwiftのperformSelectorを置き換えるこの拡張機能を追加する必要があり、メカニズムの構築に役立ちます。

extension NSObject {
    func callSelector(selector: Selector, object: AnyObject?, delay: NSTimeInterval) {
        let delay = delay * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

        dispatch_after(time, dispatch_get_main_queue(), {
            NSThread.detachNewThreadSelector(selector, toTarget:self, withObject: object)
        })
    }
}

2015年6月15日更新:Objective-C

誰かがObjective-Cのコードを要求したので、ここにあります。今は時間がないので、実行しませんが、非常に簡単です。

UIResponder *responder = self;
while(responder){
    if ([responder respondsToSelector: @selector(OpenURL:)]){
        [responder performSelector: @selector(OpenURL:) withObject: [NSURL URLWithString:@"www.google.com" ]];
    }
    responder = [responder nextResponder];
}

前述のように、このObjective-Cコードは実行していません。Swiftコードからの単なる変換です。問題や解決策が発生した場合はお知らせください。更新します。現在、私はSwiftを使用していますが、残念ながら私の脳はObjective-Cを非推奨にしています

2016年5月2日更新:非推奨の機能

@KyleKIMが指すように、セレクター関数はSwift 2.2で#selectorに置き換えられました。また、非推奨の関数があり、おそらくSwift 3.0で削除される可能性があるため、代替を見つけるためにいくつかの研究を行っています。

2016年9月16日更新:XCode 8、Swift 3.0およびiOS10次のコードは、言及されたバージョンでまだ動作しています。次の警告が表示されます。

let url = NSURL(string:urlString)
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)

var responder = self as UIResponder?

while (responder != nil){
    if responder?.responds(to: Selector("openURL:")) == true{
        responder?.perform(Selector("openURL:"), with: url)
    }
    responder = responder!.next
}

アップデート6/15/2017:XCode 8.3.3

let url = NSURL(string: urlString)
let selectorOpenURL = sel_registerName("openURL:")
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)

var responder = self as UIResponder?

while (responder != nil){
    if responder?.responds(to: selectorOpenURL) == true{
        responder?.perform(selectorOpenURL, with: url)
    }
    responder = responder!.next
}
41
Julio Bailon

このコードを試してください。

    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil)
    {
        NSLog(@"responder = %@", responder);
        if([responder respondsToSelector:@selector(openURL:)] == YES)
        {
            [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:urlString]];
        }
    }
25
DongHyun Jang

Appleは、ホストアプリが使用する「同じ」コードである次のソリューションを受け入れました。これまでのすべてのiOS 8バージョンで動作します(iOS 8.0-iOS 8.3でテスト済み)。

NSURL *destinationURL = [NSURL URLWithString:@"myapp://"];

// Get "UIApplication" class name through ASCII Character codes.
NSString *className = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x55, 0x49, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E} length:13] encoding:NSASCIIStringEncoding];
if (NSClassFromString(className)) {
    id object = [NSClassFromString(className) performSelector:@selector(sharedApplication)];
    [object performSelector:@selector(openURL:) withObject:destinationURL];
}
16
Hank Brekke

Swift 3.0&4.0:

// For skip compile error. 
func openURL(_ url: URL) {
    return
}

func openContainerApp() {
    var responder: UIResponder? = self as UIResponder
    let selector = #selector(openURL(_:))
    while responder != nil {
        if responder!.responds(to: selector) && responder != self {
            responder!.perform(selector, with: URL(string: "containerapp://")!)
            return
        }
        responder = responder?.next
    }
}

説明:

拡張では、APIはコンパイラによって制限され、コンテナアプリのようにopenURl(:URL)を使用できないようにします。ただし、APIはまだここにあります。

そして、クラスでメソッドを宣言するまでメソッドを実行できません。本当に必要なのは、UIApplicationにこのメソッドを実行させることです。

レスポンダーチェーンを思い出してください。

    var responder: UIResponder? = self as UIResponder
    responder = responder?.next

uIApplicationオブジェクトにループします。

また、このメソッドを使用したアプリはレビュープロセスに合格するため、使用する心配はありません。

13
Alen Liang

Working solution(iOS 9.2でテスト済み)キーボード拡張用。このカテゴリは、非表示のsharedApplicationオブジェクトにアクセスするための特別なメソッドを追加し、そのオブジェクトでopenURL:を呼び出します。 (もちろん、アプリスキームでopenURL:メソッドを使用する必要があります。)

extension UIInputViewController {

    func openURL(url: NSURL) -> Bool {
        do {
            let application = try self.sharedApplication()
            return application.performSelector("openURL:", withObject: url) != nil
        }
        catch {
            return false
        }
    }

    func sharedApplication() throws -> UIApplication {
        var responder: UIResponder? = self
        while responder != nil {
            if let application = responder as? UIApplication {
                return application
            }

            responder = responder?.nextResponder()
        }

        throw NSError(domain: "UIInputViewController+sharedApplication.Swift", code: 1, userInfo: nil)
    }

}
8

NSExtensionContextは、今日の拡張機能でopenURL関数のみをサポートしています。これは、NSExtensionContextに関するAppleのドキュメントに記載されています。元の言葉は、「各拡張ポイントはポイントはこの方法をサポートしています。」

7
fiona zhou

ドキュメントは次のように言っているので、バグのようです。

含まれているアプリを開く

場合によっては、拡張機能が含まれているアプリを開くように要求することは理にかなっています。たとえば、OS Xのカレンダーウィジェットは、ユーザーがイベントをクリックするとカレンダーを開きます。含まれているアプリがユーザーの現在のタスクのコンテキストで意味のある方法で開くようにするには、アプリとその拡張機能の両方が使用できるカスタムURLスキームを定義する必要があります。

拡張機能は、含まれているアプリを直接開くように指示しません。代わりに、NSExtensionContextのopenURL:completionHandler:メソッドを使用して、システムに含まれているアプリを開くように指示します。拡張機能がこのメソッドを使用してURLを開くと、システムはリクエストを実行する前に検証します。

今日報告しました: http://openradar.appspot.com/17376354 暇があれば、それをduすべきです。

7
Arek Holko

考えられる回避策:小さなUIWebViewを作成してビューに追加し、上記で設定したURLスキームでメソッドloadRequestを実行します。これは回避策であり、Appleがそれについて何を言うかわかりません。幸運を祈ります!

6
nurxyz

最新のSwift構文を使用したJulio Bailonの回答の更新バージョン:

let url = NSURL(string: "scheme://")!
var responder: UIResponder? = self
while let r = responder {
    if r.respondsToSelector("openURL:") {
        r.performSelector("openURL:", withObject: url)
        break
    }
    responder = r.nextResponder()
}

現在NSObjectの拡張機能は必要ありません。

注:このコードを呼び出す前に、ビューがビュー階層にアタッチされるのを待つ必要があります。そうしないと、レスポンダーチェーンを使用できません。

4
Pulsar

次のコードはXcode 8.3.3、iOS10、Swift3およびXcode 9、iOS11、Swift4コンパイラの警告なし:

func openUrl(url: URL?) {
    let selector = sel_registerName("openURL:")
    var responder = self as UIResponder?
    while let r = responder, !r.responds(to: selector) {
        responder = r.next
    }
    _ = responder?.perform(selector, with: url)
}

func canOpenUrl(url: URL?) -> Bool {
    let selector = sel_registerName("canOpenURL:")
    var responder = self as UIResponder?
    while let r = responder, !r.responds(to: selector) {
        responder = r.next
    }
    return (responder!.perform(selector, with: url) != nil)
}

アプリがユニバーサルリンクをサポートしていることを確認してください。サポートしていない場合、ブラウザーでリンクが開きます。詳細はこちら: https://developer.Apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

3
Deniss Fedotovs

最新のiOS SDK 10.2のソリューション。以前のソリューションはすべて、非推奨のAPIを使用しています。このソリューションは、ホストアプリケーション(拡張機能の実行コンテキストを作成するこのアプリ)のUIApplication UIResponderの検索に基づいています。呼び出す引数は3つあり、performSelector:メソッドでは実行できないため、Objective-Cでのみソリューションを提供できます。この非推奨ではないメソッドopenURL:options:completionHandler:を呼び出すには、Swiftでは利用できないNSInvocationインスタンスを使用する必要があります。提供されたソリューションは、Objective-CおよびSwift(すべてのバージョン)から呼び出すことができます。提供されたソリューションがAppleレビュープロセス。

UIViewController+OpenURL.h

#import <UIKit/UIKit.h>
@interface UIViewController (OpenURL)
- (void)openURL:(nonnull NSURL *)url;
@end

UIViewController+OpenURL.m

#import "UIViewController+OpenURL.h"

@implementation UIViewController (OpenURL)

- (void)openURL:(nonnull NSURL *)url {

    SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:");

    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil) {
        NSLog(@"responder = %@", responder);
        if([responder respondsToSelector:selector] == true) {
            NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector];
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];

            // Arguments
            NSDictionary<NSString *, id> *options = [NSDictionary dictionary];
            void (^completion)(BOOL success) = ^void(BOOL success) {
                NSLog(@"Completions block: %i", success);
            };

            [invocation setTarget: responder];
            [invocation setSelector: selector];
            [invocation setArgument: &url atIndex: 2];
            [invocation setArgument: &options atIndex:3];
            [invocation setArgument: &completion atIndex: 4];
            [invocation invoke];
            break;
        }
    }
}

@end

From Swift 3これは、View ControllerがView階層にある場合にのみ実行できます。これは私が使用しているコードです:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let context = self.extensionContext!
        let userAuthenticated = self.isUserAuthenticated()

        if !userAuthenticated {
            let alert = UIAlertController(title: "Error", message: "User not logged in", preferredStyle: .alert)
            let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
                context.completeRequest(returningItems: nil, completionHandler: nil)
            }
            let login = UIAlertAction(title: "Log In", style: .default, handler: { _ in
                //self.openContainingAppForAuthorization()
                let url = URL(string: "fashionapp://login")!
                self.open(url)
                context.completeRequest(returningItems: nil, completionHandler: nil)
            })

            alert.addAction(cancel)
            alert.addAction(login)
            present(alert, animated: true, completion: nil)
        }
    }
3
Marcin Kapusta

Today Extensionのみが機能しているようです。
100%文書化されているわけではありませんが、Apple従業員はキーボード拡張機能がopenURL:completionHandlerをサポートしていないと明確に述べています。
ドキュメントには次のように書かれています:

各拡張ポイントは、このメソッドをサポートするかどうか、またはこのメソッドをサポートする条件を決定します。

そのため、実際には、共有、アクション、キーボード、およびドキュメントプロバイダーはnotだれでも(ベータ5)動作し、Today Extensionのみがサポートします。

0
n8tr

私の推測では、これは意図的には不可能です。 openURL:completionHandler:ブロックは、すべての拡張機能タイプでサポートされているわけではないことを示しており、アクション拡張機能のドキュメントでは次のように明示されています。

ユーザーがソーシャルWebサイトでコンテンツを共有したり、関心のある情報の更新をユーザーに提供したい場合、アクション拡張ポイントは適切な選択ではありません。

共有拡張機能の方が適切かもしれませんが、両方のタイプのドキュメントでは、ユーザーをアプリに連れて行くのではなく、ホストアプリにエクスペリエンスを埋め込む必要があるため、それも許可されない可能性があります。それでは、おそらく共有拡張機能のドキュメントに従って、提案されているように拡張機能UIから画像をアップロードするだけですか?

0
Jesse Rusak

すべてのアプリ拡張機能タイプが「extensionContext openURL」をサポートしているわけではありません。

IOS 8ベータ4でテストしたところ、Today拡張機能がサポートしていますが、キーボード拡張機能はサポートしていません。

0
Vince Yuan

As Apple document "Todayウィジェット(および他のアプリ拡張機能タイプはありません)は、NSExtensionContextクラスのopenURL:completionHandler:メソッドを呼び出すことにより、含まれるアプリを開くようシステムに要求できます。」

他の拡張機能には、このソリューションを使用しました

UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"ownApp://"; // Use other application url schema.

NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv='refresh' content='0; URL=%@'></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:1.0];
0
Anand Mishra