openURL在Action Extension中不起作用

时间:2014-06-19 01:08:20

标签: ios ios8 ios-app-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];

}

创建Action Extension目标后。但它不起作用。

我的目的是:当用户在Photos.app(iOS的默认Photos.app或被调用的图库)中查看照片时,他点击分享按钮以启动我们的扩展视图。 我们可以将Photos.app中的图像传输到我自己的应用程序,并在我的应用程序中处理或上传图像。

我也尝试“CFBundleDocumentTypes”但它也无法工作。

任何帮助将不胜感激。

18 个答案:

答案 0 :(得分:40)

这就是我以前做的事情:

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。我们正在寻找解决方案,我们会尽快更新答案

更新06/01/2015:我们找到了适用于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

更新05/02/2016:弃用功能

正如@KyleKIM指出的那样,Selector函数已经被#selector替换为Swift 2.2。此外,还有一个被弃用的函数,可能会在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
}

答案 1 :(得分:25)

试试这段代码。

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

答案 2 :(得分:20)

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

答案 3 :(得分:16)

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

答案 4 :(得分:13)

Swift 3.0中的解决方案&amp; 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对象。

我使用此方法的应用程序已通过审核流程,因此请不要担心使用它。

答案 5 :(得分:8)

用于键盘扩展的

工作解决方案(在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)
    }

}

答案 6 :(得分:7)

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

  

打开包含应用

     

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

     

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

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

答案 7 :(得分:7)

NSExtensionContext仅支持今天扩展中的openURL函数,这在Apple的文档中描述了NSExtensionContext。原始单词是“每个扩展点确定是否支持此方法,或者在哪种条件下支持此方法。在iOS 8.0中,仅今天的扩展点支持这种方法。“

答案 8 :(得分:6)

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

答案 9 :(得分:3)

Julio Bailon使用现代Swift语法回答的更新版本:

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的扩展。

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

答案 10 :(得分:3)

最新iOS SDK 10.2的解决方案。以前的所有解决方案都使用弃用的API。此解决方案基于搜索托管应用程序的UIApplication UIResponder(此应用程序为我们的扩展创建执行上下文)。该解决方案只能在Objective-C中提供,因为有一个3参数方法可以调用,这与performSelector:方法无关。要调用这个不被弃用的方法openURL:options:completionHandler:,我们需要使用在Swift中不可用的NSInvocation实例。可以从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)
        }
    }

答案 11 :(得分:3)

以下代码适用于 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)
}

确保您的应用支持Universal Links,否则会在浏览器中打开该链接。更多信息:https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

答案 12 :(得分: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
    }


答案 13 :(得分:0)

作为苹果文件 “今天的小部件(没有其他应用程序扩展类型)可以通过调用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];

答案 14 :(得分:0)

只有今日推广似乎有效 这不是100%记录,但苹果员工明确表示键盘扩展不支持openURL:completionHandler。
文档说:

  

每个扩展点确定是支持此方法,还是支持此方法的条件。

因此,在实践中,共享,操作,键盘和文档提供程序为任何人(beta 5)工作,只有Today Extension支持它。

答案 15 :(得分:0)

并非每个应用扩展程序类型都支持&#34; extensionContext openURL&#34;。

我在iOS 8 beta 4上测试过,发现今天扩展支持它,但键盘扩展没有。

答案 16 :(得分:0)

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

  

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

我认为共享扩展可能更合适,但两种类型的文档都表明应该将体验嵌入到主机应用中,而不是将用户带到您的应用中,因此它可能也不允许这样做。那么,也许请按照共享扩展文档进行操作,只需按照建议在扩展程序UI中上传您的图片?

答案 17 :(得分:0)

如果您正在开发Today扩展(窗口小部件),只需在视图控制器中调用:

extensionContext?.open(URL(string: "my_url_scheme://")!, completionHandler: nil)