53

I add following code:

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

}

after I create the Action Extension target. But it can not work.

My purpose is that: when user view a photo in Photos.app (the iOS's default Photos.app or called gallery), and he click the share button to launch our extension view. We can transfer the image from Photos.app to my own app and deal or upload the image in my app.

I also try "CFBundleDocumentTypes" but it also can not work.

Any help will be appreciated.

4

17 回答 17

44

这是我以前做的:

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 方案

更新 04/17/2015:这不适用于 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。

您需要添加此扩展来替换 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
}
于 2015-01-20T03:28:54.370 回答
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]];
        }
    }
于 2015-04-10T06:16:11.490 回答
20

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

于 2014-07-12T05:12:11.023 回答
18

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];
}
于 2015-06-22T12:29:07.647 回答
15

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

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

于 2016-11-18T10:55:40.880 回答
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)
}

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

于 2017-06-22T08:55:34.677 回答
9

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

}
于 2015-12-23T00:34:43.410 回答
7

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

打开包含的应用程序

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

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

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

于 2014-06-19T11:36:28.167 回答
7

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

于 2014-10-16T04:26:44.570 回答
6

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

于 2014-07-07T15:45:45.130 回答
6

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

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

于 2015-10-27T11:03:38.167 回答
3

最新 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)
        }
    }
于 2017-03-18T21:13:20.187 回答
2

使用递归的更安全的选择

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
    }


于 2020-08-17T08:51:09.683 回答
0

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

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

于 2014-08-02T18:34:13.457 回答
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];
于 2015-01-28T06:12:19.933 回答
0

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

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

所以在实践中,共享、操作、键盘和文档提供程序对任何人都不起作用(测试版 5),只有 Today Extension 支持它。

于 2014-08-07T18:22:29.587 回答
-2

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

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

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

于 2014-06-19T02:04:37.370 回答