openURL not work in Action Extension

This is what I used to do:

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

Please note that in this case I am instantiating this call from the UIInputViewController.

This method should also work using the URL scheme from the containing app

UPDATE 04/17/2015: This does not work with iOS 8.3. We are looking for a solution and we will update the answer soon

UPDATE 06/01/2015: We found a solution that works in 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()
}

This will find a suitable responder to send the openURL to.

You need to add this extension that replaces the performSelector for swift and helps in the construction of the mechanism:

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

UPDATE 06/15/2015: Objective-C

Someone asked for the code in Objective-C so here it is. I am not going to run it as I don't have the time right now but it should be quite straightforward:

UIResponder *responder = self;
while(responder){
    if ([responder respondsToSelector: @selector(OpenURL:)]){
        [responder performSelector: @selector(OpenURL:) withObject: [NSURL URLWithString:@"www.google.com" ]];
    }
    responder = [responder nextResponder];
}

As mentioned, I have not run this Objective-C code, it is just a conversion from the Swift code. Please let me know if you encounter any issues and the solution and I will update it. Nowadays, I am just using swift and unfortunately my brain is deprecating Objective-C

UPDATE 05/02/2016: Deprecated functions

As pointed by @KyleKIM the Selector functions have been replaced in Swift 2.2 by #selector. Also, there is a function that is deprecated and will probably get removed in Swift 3.0 so I am doing some research to find an alternative.

UPDATE 09/16/2016: XCode 8, Swift 3.0 and iOS10 The following code is still working on the mentioned versions. You will get some warnings:

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
}

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

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

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


Apple accepted the following solution, which is the "same" code that a host app would use. It works on all iOS 8 versions to date (tested on 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];
}

Worked solution in 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
    }
}

Explanation:

In extension, api is limited by compiler to not let you use openURl(:URL) like in container app. However the api is still here.

And we can't perform method in our class until we declare it, what we really want is let UIApplication to perform this method.

Recall to responder chain, we can use

    var responder: UIResponder? = self as UIResponder
    responder = responder?.next

to loop to UIApplication object.

And my apps with this method pass the review process, so don't worry to use it.