openURL在Action Extension中不起作用

Lau*_*Fan 46 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];

}
Run Code Online (Sandbox Code Playgroud)

在我创建Action Extension目标之后.但它无法奏效.

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

我也尝试"CFBundleDocumentTypes"但它也无法工作.

任何帮助将不胜感激.

Jul*_*lon 42

这就是我以前做的事情:

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];
Run Code Online (Sandbox Code Playgroud)

请注意,在这种情况下,我将从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()
}
Run Code Online (Sandbox Code Playgroud)

这将找到一个合适的响应者来发送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)
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

更新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];
}
Run Code Online (Sandbox Code Playgroud)

如上所述,我没有运行这个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
}
Run Code Online (Sandbox Code Playgroud)

更新日期: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
}
Run Code Online (Sandbox Code Playgroud)

  • @JulioBailon所以这段代码实际上做的是它寻找`UIApplication`,因为那是实现`openURL`的唯一对象.通常,当您想要从应用程序打开URL时,您使用`sharedApplication`但它被注释为无法从扩展中访问.但是,iOS无法阻止您在响应程序链中查找它.整齐. (3认同)
  • @JulioBailon代码成功构建并到达`if([responder respondsToSelector:@selector(openURL:)]){`内部,但是带有对象的[responder performSelector:@selector(openURL :))行:[NSURL URLWithString:@“ www .google.com“]];`不做任何事情 (2认同)
  • 我使用Xcode7.3.1 GM,它给我黄色警告"使用'#selector'而不是显式构建'选择器'"并建议将Selector("openURL:")更改为#selector(UIApplication.openURL(_ :) ).这样做会发生RED错误'opneURL'不可用.通过一些挖掘,我看到"从字符串文字构造选择器已弃用,将在Swift 3.0中删除".我们应该做什么? (2认同)

小智 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]];
        }
    }
Run Code Online (Sandbox Code Playgroud)


小智 20

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

  • 这似乎与Apple自己的文档相矛盾https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html#//apple_ref/doc/uid/TP40014214-CH2-SW2 (6认同)
  • 对于Share Extensions来说,这似乎也是如此.你确定吗?我成功发布后尝试启动共享内容.有没有办法为用户提供确认共享成功? (3认同)
  • 除了向互联网发送内容之外,共享扩展实际上是无用的? (3认同)
  • 你是苹果员工吗?谢谢你的回答。 (2认同)
  • 应用程序启动器的事情是有道理的.然而,似乎这里有一个功能漏洞,因为提供一个特定的API来启动容器应用程序是有用的,因为有一个保证扩展容器应用程序关系. (2认同)

Han*_*kke 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];
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*ang 14

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
    }
}
Run Code Online (Sandbox Code Playgroud)

说明:

在扩展中,api受编译器限制,不允许您像容器应用程序一样使用openURl(:URL).然而,api仍然在这里.

在我们声明它之前我们不能在我们的类中执行方法,我们真正想要的是让UIApplication执行这个方法.

回想一下响应者链,我们可以使用

    var responder: UIResponder? = self as UIResponder
    responder = responder?.next
Run Code Online (Sandbox Code Playgroud)

循环到UIApplication对象.

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


Den*_*ovs 10

以下代码适用于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)
}
Run Code Online (Sandbox Code Playgroud)

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


Val*_*gin 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)
    }

}
Run Code Online (Sandbox Code Playgroud)


Are*_*lko 7

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

打开包含应用程序

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

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

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

  • Apple改变了它的文档:`在某些情况下,今天的小部件可以请求打开包含的应用程序 (2认同)

小智 7

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


det*_*man 6

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

  • @LaurenceFan有没有人使用此解决方法提交了应用程序?我很想知道Apple是否接受了它. (4认同)
  • 此功能在iOS 8.3中破裂. (3认同)

Pul*_*sar 6

使用现代 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()
}
Run Code Online (Sandbox Code Playgroud)

现在不需要 NSObject 的扩展。

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