openURL not work in Action Extension

2019-01-02 14:41发布

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.

16条回答
孤独总比滥情好
2楼-- · 2019-01-02 15:17

Not every app extension type supports "extensionContext openURL".

I tested on iOS 8 beta 4 and found Today extension supports it, but keyboard extension does not.

查看更多
与风俱净
3楼-- · 2019-01-02 15:18

This is by design. We don't want Custom Actions to become app launchers.

查看更多
不流泪的眼
4楼-- · 2019-01-02 15:20

Following code works on Xcode 8.3.3, iOS10, Swift3 and Xcode 9, iOS11, Swift4 without any compiler warnings:

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

Make sure your app supports Universal Links, otherwise it will open the link in browser. More info here: https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

查看更多
无与为乐者.
5楼-- · 2019-01-02 15:21

Try this code.

    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]];
        }
    }
查看更多
浮光初槿花落
6楼-- · 2019-01-02 15:21

Solution for the latest iOS SDK 10.2. All previous solutions use deprecated api. This solution is based on searching UIApplication UIResponder of the hosting application (This app which create execution context for our extension). The solution can only be provided in Objective-C because there is a 3 arguments method to invoke and this is impossible to do with performSelector: methods. To invoke this not deprecated method openURL:options:completionHandler: we need to use NSInvocation instance which is unavailable in Swift. The provided solution can be invoked from Objective-C and Swift (any version). I need to say that I don't know yet if provided solution will be valid for apple review process.

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 You can execute this only if Your view controller is in view hierarchy. This is the code how I'm using it:

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)
        }
    }
查看更多
长期被迫恋爱
7楼-- · 2019-01-02 15:24

It seems to be a bug, because docs say:

Opening the Containing App

In some cases, it can make sense for an extension to request its containing app to open. For example, the Calendar widget in OS X opens Calendar when users click an event. To ensure that your containing app opens in a way that makes sense in the context of the user’s current task, you need to define a custom URL scheme that both the app and its extensions can use.

An extension doesn’t directly tell its containing app to open; instead, it uses the openURL:completionHandler: method of NSExtensionContext to tell the system to open its containing app. When an extension uses this method to open a URL, the system validates the request before fulfilling it.

I reported it today: http://openradar.appspot.com/17376354 You should dupe it, if you have some free time.

查看更多
登录 后发表回答