【问题标题】:openURL not work in Action ExtensionopenURL 在操作扩展中不起作用
【发布时间】:2014-08-09 10:35:00
【问题描述】:

我添加以下代码:

- (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”,但它也无法正常工作。

任何帮助将不胜感激。

【问题讨论】:

  • openURL from Today Extension 的可能重复项
  • 您还必须在应用的信息/属性区域中添加一个 URL 方案。请参阅“应用程序间通信”和“使用 URL 方案与应用程序通信”的文档。
  • 您好,我正在使用 tableviewcontroller 但无法在此类中使用 extensionContext。有人可以帮我解决这个问题吗
  • 对于任何寻求更多说明的人; Apple 在文档中非常清楚什么可以打开和不可以打开 URL (developer.apple.com/library/ios/documentation/General/…)。该文档中的相关文本:A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class. cmets 下面是一个有趣的解决方法。

标签: ios ios8 ios-app-extension


【解决方案1】:

使用递归的更安全的选择

func handleUrl(_ hostUrl: URL?) {
        fileUrl.map { URL(string: yourUrlSchemeId + "://" + $0.absoluteString) }.map { finalUrl in
            let selector = #selector(openURL(_:))
            func proccessNext(_ responder: UIResponder?) {
                responder.map {
                    if $0.responds(to: selector) {
                        $0.perform(selector, with: finalUrl)
                    } else {
                        proccessNext($0.next)
                    }
                }
            }
            proccessNext(self.next)
        }
    }
    @objc func openURL(_ url: URL) {
        return
    }


【讨论】:

    【解决方案2】:

    在 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 对象。

    我使用这种方法的应用通过了审核流程,所以不用担心使用它。

    【讨论】:

    • 虽然此代码 sn-p 可以解决问题,包括解释 really helps 以提高您的帖子质量。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请edit您的答案添加解释,并说明适用的限制和假设。
    • 此代码适用于我的 Safari 共享扩展。知道Apple是否可以这样做吗?如果可以安全使用,我希望将其投入生产。
    • @timgcarlson 得到答案了吗?还是自己测试?
    • @Reiz3N 它确实有效,但我还没有提交包含此代码的应用程序。我很犹豫要不要这样做,因为我从未见过其他应用这样做。
    • 很好的答案@AlenLiang,但我对应用商店审查过程有一个担忧。应用上述实施是否会获得批准?
    【解决方案3】:

    以下代码可在 Xcode 8.3.3、iOS10、Swift3Xcode 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

    【讨论】:

    • 使用 xcode 9、swift 3 和 iOS 10.3.1 的自定义共享扩展 UI(直接子类化 UIViewController)不适用于我
    • @KauthamMurugan,刚刚检查过,在 xcode 9、swift 4 和 iOS 11 上运行良好。
    • 您是否将 ShareViewController 中的 SLComposeServiceViewController 替换为 UIViewController?我认为这应该是问题所在,但我不确定。
    • 这段代码在 iMessage 扩展中完美地为我工作。在 iOS 13 上测试。我使用此代码打开了应用商店评论 URL。谢谢!
    • 我正在使用 Swift 5.4 和 Xcode 12.5,这段代码对我有用!谢谢@DenissFedotovs
    【解决方案4】:

    这是我以前做的:

    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。

    您需要添加这个扩展来替换 performSelector 以实现 swift 并帮助构建机制:

    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 所指出的,Selector 函数在 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
    }
    

    2017 年 6 月 15 日更新: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
    }
    

    【讨论】:

    • @JulioBailon 代码成功构建并到达if ([responder respondsToSelector: @selector(openURL:)]){ 内部,但[responder performSelector: @selector(openURL:) withObject: [NSURL URLWithString:@"www.google.com" ]]; 行没有做任何事情
    • @JulioBailon 我尝试移除中断...反正只有 1 个响应者进入那里,因此移除中断没有任何作用。感谢您的帮助
    • @JulioBailon 所以这段代码实际上是寻找UIApplication,因为这是唯一实现openURL的对象。通常,当您想从应用程序打开 URL 时,您使用 sharedApplication 但它被注释为不可从扩展访问。但是 iOS 不能阻止您在响应者链中查找它。整洁。
    • 我正在使用 Xcode7.3.1 GM,它给了我黄色警告“使用 '#selector' 而不是显式构建 'Selector' ”并建议将 Selector("openURL:") 更改为 #selector (UIApplication.openURL(_:))。这样做会发生 RED 错误“opneURL”不可用。经过一番挖掘,我看到“从字符串文字构造选择器已弃用,将在 Swift 3.0 中删除”。我们该怎么办?
    • 似乎 UPDATE 09/16/2016 中的解决方案不再起作用(在 Xcode 8.3.2 中)
    【解决方案5】:

    最新 iOS SDK 10.2 的解决方案。以前的所有解决方案都使用已弃用的 api。此解决方案基于搜索托管应用程序的 UIApplication UIResponder(此应用程序为我们的扩展创建执行上下文)。该解决方案只能在 Objective-C 中提供,因为要调用 3 个参数的方法,而 performSelector: 方法无法做到这一点。要调用这个未被弃用的方法openURL:options:completionHandler:,我们需要使用 NSInvocation 实例,它在 Swift 中不可用。提供的解决方案可以从 Objective-C 和 Swift(任何版本)调用。我需要说,我还不知道提供的解决方案是否适用于苹果审核流程。

    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
    

    从 Swift 3 开始,只有当您的视图控制器位于视图层次结构中时,您才能执行此操作。这是我如何使用它的代码:

    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)
            }
        }
    

    【讨论】:

    • 它确实有效!测试 Xcode 8.3.1 + swift 3 + iPhone 5s + iOS 10.3.2
    • 从 iOS 13.1.3 中的共享扩展应用程序单击,它不起作用。你有解决办法吗?
    【解决方案6】:

    适用于键盘扩展的工作解决方案(在 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)
        }
    
    }
    

    【讨论】:

    【解决方案7】:

    使用现代 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 扩展。

    注意:在调用此代码之前,您必须等待视图附加到视图层次结构,否则无法使用响应者链。

    【讨论】:

    • 谢谢 - 我已经有 3 小时的 Swift 和 iOS 使用经验,所以这真的很有帮助 :)
    • "注意:在调用此代码之前,您必须等待视图附加到视图层次结构,否则无法使用响应者链。"这个非常重要。我浪费了很多时间,直到我意识到从 viewDidLoad 调用不起作用但 viewWillAppear 确实起作用了。
    【解决方案8】:

    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];
    }
    

    【讨论】:

    • 你在 iOS 9 上测试过这个吗?
    • @SebastienLorber 刚刚在 iOS 9 上进行了测试,这个解决方案仍然有效。需要注意的是,现在 iOS 9 在切换应用程序之前会显示确认消息,因此运行此代码将首先显示一个确认对话框,询问:Open "MyApp"?
    • 碰巧,你能解释一下你的解决方案吗(我的意思主要是 ASCII 字符)?也许提供类似的 Swift 解决方案?在这里查看我的问题:stackoverflow.com/questions/33153626/…
    【解决方案9】:

    试试这个代码。

        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]];
            }
        }
    

    【讨论】:

    • 顺便说一句,为什么这行得通?我认为扩展无法获取当前正在运行的应用程序的实例。
    • 这是阻止扩展被终止:(
    【解决方案10】:

    作为苹果文档 “ 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];
    

    【讨论】:

      【解决方案11】:

      NSExtensionContext在今天的扩展中只支持openURL功能,这在苹果关于NSExtensionContext的文档中有描述。原文是“每个扩展点决定是否支持这个方法,或者在什么条件下支持这个方法。在iOS 8.0中,只有Today 扩展点支持这种方法。”

      【讨论】:

        【解决方案12】:

        似乎只有 Today Extension 有效。
        它不是 100% 记录在案的,但一位苹果员工明确表示键盘扩展不支持 openURL:completionHandler。
        文档说:

        每个扩展点决定是否支持这种方法,或者在什么条件下支持这种方法。

        因此,在实践中,Share、Action、Keyboard 和 Document 提供程序适用于任何人(测试版 5),只有 Today Extension 支持它。

        【讨论】:

          【解决方案13】:

          并非所有应用扩展类型都支持“extensionContext openURL”。

          我在 iOS 8 beta 4 上测试,发现 Today 扩展支持,但键盘扩展不支持。

          【讨论】:

          • 对,但它没有说明什么类型支持它
          【解决方案14】:

          这是设计使然。我们不希望自定义操作成为应用启动器。

          【讨论】:

          • 似乎共享扩展也是如此。你可否确认?成功发布后,我试图启动共享内容。有什么方法可以向用户提供分享成功的确认信息?
          • 您是 Apple 员工吗?谢谢你的回答。
          • 应用启动器的事情很有意义。但是,这里似乎存在一个功能漏洞,如果提供特定的 API 来启动容器应用程序会很有用,因为容器应用程序关系有保证的扩展。
          • 那么除了向互联网发送一些东西之外,共享扩展实际上是没有用的吗?
          • 这似乎与苹果自己的文档相矛盾developer.apple.com/library/ios/documentation/General/…
          【解决方案15】:

          一种可能的解决方法: 创建一个小的 UIWebView 并将其添加到您的视图中,并使用您在上面设置的 url 方案运行它的方法 loadRequest。 这是一种解决方法,我不确定 Apple 会怎么说。 祝你好运!

          【讨论】:

          • 很高兴看到这个答案!我的领导也建议这种解决方法。我已经尝试过了,让 UIWebView 运行一个 javaScript 来自动模拟“单击跳转链接”动作。而且我可以成功。
          • 我的回答对你有帮助吗? :)
          • 它提醒我,也许苹果不会允许这样的行为。正如@IanBaird 提到的,苹果似乎真的不赞成它。我投票给你的答案。但我认为我们现在无法解决这个问题。你这么认为吗?
          • @LaurenceFan 有没有人使用此解决方法提交了应用程序?我很想知道 Apple 是否接受它。
          • 此功能在 iOS 8.3 中中断。
          【解决方案16】:

          这似乎是一个错误,因为文档说:

          打开包含的应用程序

          在某些情况下,扩展程序请求其 包含要打开的应用程序。例如,OS X 中的日历小部件打开 用户单击事件时的日历。确保您的包含应用程序 以在用户当前的上下文中有意义的方式打开 任务,您需要定义一个自定义 URL 方案,应用程序及其 扩展可以使用。

          扩展程序不会直接告诉其包含的应用程序打开; 相反,它使用 openURL:completionHandler: 方法 NSExtensionContext 告诉系统打开其包含的应用程序。什么时候 扩展使用此方法打开 URL,系统验证 在完成之前请求。

          我今天报告了它:http://openradar.appspot.com/17376354如果你有空闲时间,你应该欺骗它。

          【讨论】:

          • 感谢您告诉我您遇到了同样的问题。 “骗子”是什么意思?你的意思是我应该在你的错误报告中添加评论?
          • 抱歉不清楚。您应该在bugreport.apple.com 报告它并在附加说明 字段中写:It's a duplicate of rdar://17376354。指向问题的错误报告越多,Apple 修复它的机会就越大。
          • 我设法按照您提到的步骤进行操作,哈哈。我还写信给 Apple 的传道者,这在 WWDC 扩展视频的末尾显示。希望苹果能给我们一些回应。
          • 你有什么进展吗?布道者没有回应我。
          • Apple 已更改其文档:In some cases, it can make sense for a Today widget to request its containing app to open.
          【解决方案17】:

          我的猜测是这是故意不可能的。 openURL:completionHandler: 块表示可能并非所有扩展类型都支持它,并且操作扩展文档明确表示:

          如果您想帮助用户在社交网站上分享内容或向用户提供他们关心的信息的更新,Action 扩展点不是正确的选择。

          我认为共享扩展可能更合适,但两种类型的文档都建议将体验嵌入到宿主应用程序中,而不是将用户带到您的应用程序,因此它可能也不允许这样做。那么,也许可以按照分享扩展文档的建议,从扩展 UI 中上传您的图片?

          【讨论】:

          • 我再次阅读 Apple 文档,找不到“两种类型都建议将体验嵌入到宿主应用程序中”的信息。你在哪里看到的?或者您能否将原始文字粘贴到 Apple 文档中,我可以通过 Google 搜索它们? :)
          • @LaurenceFan “当用户选择您的 Share 扩展程序时,您会显示一个视图,他们在其中编写内容并发布内容”和“在 iOS 中,一个 Action 扩展程序......始终出现在操作表中或全屏模式视图”。请注意,两者都没有提及启动主机应用程序;不过,这当然不是明确的。
          • 我想我理解你的意思,我在文档中找到了这样的词。非常感谢。我们需要修改图片并做一些业务逻辑,所以仅仅使用 Share 扩展来上传图片是不够的。你认为苹果会提供更多的解释和例子吗?
          • @LaurenceFan 您应该能够在共享扩展中修改图像并执行业务逻辑。如果有某些原因您不能这样做,您应该向 Apple 提交一个错误(或编辑您现有的错误),清楚地解释为什么您需要能够为您的用例使用 openURL。
          • 我尝试使用“共享资源”将画廊的图像传输到我自己的应用程序。我会从您的建议中学习,如有必要,可能会提交错误。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-15
          • 2016-09-29
          相关资源
          最近更新 更多