openURL not work in Action Extension
Asked Answered
W

17

55

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.

Wolfson answered 19/6, 2014 at 1:8 Comment(4)
possible duplicate of openURL from Today ExtensionFlexuosity
You must also add a URL Scheme in the app's info/properties area. See documentation for "inter app communication" and "Using URL Schemes to Communicate with Apps". <posted for future searchers>Aerograph
Hi, I'm using tableviewcontroller but not able to use extensionContext in this class. can some one plz help me on thisHyperventilation
For anyone looking for more clarification; Apple is pretty clear in the docs around what can and cannot open URLs (developer.apple.com/library/ios/documentation/General/…). The relevant text from that doc: A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class. An interesting workaround is below in the comments.Pearly
V
19

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

Vidar answered 12/7, 2014 at 5:12 Comment(10)
It seems that this is also true for Share Extensions. Can you confirm? I was trying to launch to the shared content after it successfully posted. Is there any way to provide the user with confirmation the share succeeded?Pleasantry
Are you a Apple staff? Thank you for the answer.Wolfson
Is there confirmed statements from Apple regarding when "openURL" will and will not be allowed? I would like to use it in a Share and/or Action Extension only to the containing app's URL scheme. There are many valid applications for this that enable an extension to stay light, and still give the user the option to jump to the containing app if advanced options are required.Tractile
It works in Today View Extensions under certain circumstances. That's it for now.Vidar
The app launcher thing makes sense. however, It seems there's a functionality hole here were it would be useful to provide a specific API for launching the container app since there is a guaranteed extension to container app relationship.Lugger
So other than sending something to the internet, Share extensions are effectively useless?Hysterectomy
this seems to contradict Apple's own documentation developer.apple.com/library/ios/documentation/General/…Tilford
Any new information on the workaround proposed by nurne?Knighterrantry
@Pearly Your suggestion is correct. Since Apple change the document and it says, "The dotted line in Figure 2-2 represents the limited interaction available between an app extension and its containing app. A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class. " This answer looks OK up to now.https://developer.apple.com/...Wolfson
If share extension , openURL method is not called anymore, So what would be workaround to open files and process from gmail, google drive etc in main app ? Any way so far ?Diablerie
G
44

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
}
Gimlet answered 20/1, 2015 at 3:28 Comment(39)
Yes. I am not the only one using it. Another keyboard extension in the store RIFFSY uses the same code.Gimlet
burt also found an issue with ios 8.3Scarabaeid
@MatrosovAlexander what is the issue? Please shareGimlet
@JulioBailon Apple has restricted using any workarounds like UIWebView for iOS 8.3Scarabaeid
@MatrosovAlexander have you found any ways of accomplishing that without UIWebView?Gimlet
Has anyone found any ways of opening the container app from the keyboard?Parrot
@TomSawyer it works perfectly for me and for many others. Maybe I can help you if you give me some more details. Where are you running the code? Have you implemented the extension? My recommendation is to run it from the InputViewController itselfGimlet
@rob1302 I will see what I can do. Stay putGimlet
@rob1302 I converted it to Objective-C, let me know if it works for you. I have not tested it.Gimlet
@JulioBailon The code builds successfully and reaches inside if ([responder respondsToSelector: @selector(openURL:)]){ however the line of [responder performSelector: @selector(openURL:) withObject: [NSURL URLWithString:@"www.google.com" ]]; doesn't do anythingCaress
@Caress try removing the break from the while so it tries all the responders that can handle that selector. I introduced that break recently in one of the edits and that might be the issue.Gimlet
@JulioBailon I tried removing the break...there's only 1 responder that gets in there anyways so removing the break did nothing. Appreciate the help either wayCaress
@JulioBailon So what this code actually does is that it looks for UIApplication, because that is the only object which implements openURL. Normally when you want to open URL from app, you use sharedApplication but it is annotated as not accessible from extensions. However iOS can't prevent you from looking it up in the responder chain. Neat.Dispel
I can say it seems to work in iOS 9 as well, but ask the user for confirmation before opening the app.Pickle
@PavelZdenek I thought the extension is running in a separate thread. How is it possible that it can get the instance of UIApplication?Democrat
@skyline75489 You are just moving up in the hierarchy of responders until you find one that can respond to that selector. Remember the difference between thread and process. The extension belongs to a process and that process has a UIResponder that can call openURL.Gimlet
Iteration thru responders doesn't work for me. [self nextResponder] returns nil. I played a bit and if I start with self.parentViewController instead of self it sometimes works, e.g. doesn't work during the first share and does work later. I checked on iOS 9.2 (simulator and real device)Megalocardia
I am using Xcode7.3.1 GM, it gives me yellow warnings "Use '#selector' instead of explicitly constructing a 'Selector' " and suggesting to change Selector("openURL:") to #selector(UIApplication.openURL(_:)). Doing so occurs RED error 'opneURL' is unavailable. with some digging I saw "Construction of Selector from string literals deprecated and will be removed in Swift 3.0". what should we do?Osterhus
@KyleKIM good point. I am going to do some research and update the code. I will keep you posted.Gimlet
Any update on alternatives for deprecated methods? Xcode8 is already out 😬.Anecdotic
@AhmetAkkök we have submitted an extension for iOS10 in XCode8 and it still works with the following swift code: 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 }Gimlet
@Julio, I migrated from Swift 2.3 to Swift 3 and the solution works perfectly in Swift 3, thank you so much once again!Anecdotic
How to go to keyboards setting at iOS10? #33388817Anecdotic
@AhmetAkkök not sure what this has to do with this post. Opening the settings from the keyboard does not sound like a good idea. It is nice but users may consider it intrusive and therefore Apple will reject itGimlet
it used to work with openURL before iOS 10, with in the Contaning app as well as in the Keyboard (in case of a function requires full access you could send user to the settings).Anecdotic
@AhmetAkkök I know, but I understand why Apple is not permitting it now. It is intrusive.Gimlet
@Julio that may be the reason. I still hope it gets fixed. It is not easy to explain all the users how to add a new keyboard or give full access. Users need to go 4 menus under Settings 1- General 2- Keyboard, 3- Keyboards, 4- Add a new keyboard. It won't be necessary if Keyboard menu would be right under SettingsAnecdotic
Hello... @Julio Bailon your answer helped me but there is one issue...can you please take a look on : #40020021Publias
Yes, worked...but I am afraid if this warning further raised as a issues or crashes. That's why I want to solve that. And one more reason is when I upload, Apple may reject app because of deprecated code.Publias
@Publias Apple does not reject deprecated code but you are right. It is something we need to find a better solution for.Gimlet
@JulioBailon When I move to my app and come back to host app sometime (mostly second time) it shows blank screen. I am using Action extension.Can you please code on github. It will be really helpful thanks!!Mirza
You literally saved my app (which would be pointless without camera access during share action)Pointblank
it seems like the solution from UPDATE 09/16/2016 is not working anymore (in Xcode 8.3.2)Keelson
@Keelson Apple has been trying to avoid us from doing this for a while. I guess we will have to tweak it a little more againGimlet
Xcode 8.3.3 solution without warnings: https://mcmap.net/q/332549/-openurl-not-work-in-action-extensionBootle
I'm a little confused. Does this work with an iMessage extension?? And would this code go into the container app or the extension? @JulioBailonRefectory
@Refectory it is working right now on a iMessage extension (WATUU) but it was compiled with a previous version of the SDK. Not sure if this is still working but if not I will be updating the code when an upgrade occursGimlet
Thanks!! @JulioBailonRefectory
@JulioBailon is the latest Swift code still working in iOS 12? Also, does it require NSObject extension mentioned for an earlier update?Capacitate
H
25

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]];
        }
    }
Heigl answered 10/4, 2015 at 6:16 Comment(2)
Why is this working, by the way? I thought extension can not get instance of the current running app.Democrat
This is blocking extension from getting terminated :(Waltner
V
19

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

Vidar answered 12/7, 2014 at 5:12 Comment(10)
It seems that this is also true for Share Extensions. Can you confirm? I was trying to launch to the shared content after it successfully posted. Is there any way to provide the user with confirmation the share succeeded?Pleasantry
Are you a Apple staff? Thank you for the answer.Wolfson
Is there confirmed statements from Apple regarding when "openURL" will and will not be allowed? I would like to use it in a Share and/or Action Extension only to the containing app's URL scheme. There are many valid applications for this that enable an extension to stay light, and still give the user the option to jump to the containing app if advanced options are required.Tractile
It works in Today View Extensions under certain circumstances. That's it for now.Vidar
The app launcher thing makes sense. however, It seems there's a functionality hole here were it would be useful to provide a specific API for launching the container app since there is a guaranteed extension to container app relationship.Lugger
So other than sending something to the internet, Share extensions are effectively useless?Hysterectomy
this seems to contradict Apple's own documentation developer.apple.com/library/ios/documentation/General/…Tilford
Any new information on the workaround proposed by nurne?Knighterrantry
@Pearly Your suggestion is correct. Since Apple change the document and it says, "The dotted line in Figure 2-2 represents the limited interaction available between an app extension and its containing app. A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class. " This answer looks OK up to now.https://developer.apple.com/...Wolfson
If share extension , openURL method is not called anymore, So what would be workaround to open files and process from gmail, google drive etc in main app ? Any way so far ?Diablerie
A
19

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.

Anthracosis answered 18/11, 2016 at 10:55 Comment(13)
While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, not just the person asking now! Please edit your answer to add explanation, and give an indication of what limitations and assumptions apply.Ossian
This code works for my Safari Share extension. Any idea if this is ok by Apple? I'd like to get this into production if it's safe to use.Maurya
@Maurya got an answer yet? Or tested it yourself?Superior
@Reiz3N It definitely works, but I have yet to submit an app with this code in there. I'm hesitant to do so since I've never seen another app do this.Maurya
@Maurya Thanks for an answer, used this code snippet myself and it works great. Gonna use it in production app and see if Apple does have anything against it. If you'll publish yours earlier I would be grateful if you gave me a hint what they said about it ;)Superior
i am using this code in my app, and pass apple's review.Anthracosis
i get following Compiler Error: Method 'openURL' with Objective-C selector 'openURL:' conflicts with previous declaration with the same Objective-C selectorKeelson
Great answer @AlenLiang but I have one concern of app store review process. Will the app have above implementation get approved?Intrusion
@Kamarshad My app using the code above passed review.Anthracosis
@AlenLiang Thank you :)Intrusion
Awesome ! , This answer working in Swift 4 as well .Joinery
@Basil, it's not working on iOS 13. Any workaround ?Diablerie
XCode 11.5 "Use of unresolved identifier 'openURL'"Ontologism
W
18

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];
}
Windowsill answered 22/6, 2015 at 12:29 Comment(3)
Have you tested this on iOS 9 ?Pickle
@SebastienLorber just tested on iOS 9 and this solution still works. There is the caveat that now iOS 9 displays confirmation messages before switching apps, so running this code will first show a confirmation dialog asking: Open "MyApp"?Windowsill
by chance, can you explain your solution a bit (I mean the ASCII characters mostly)? And maybe provide a similar Swift solution? see my question here: #33154126Pickle
I
11

Working solution (tested on iOS 9.2) for Keyboard Extension. This category adds special method for access to hidden sharedApplication object and then call openURL: on it. (Of course then you have to use openURL: method with your app scheme.)

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

}
Illiterate answered 23/12, 2015 at 0:34 Comment(2)
I was lucky enough to read every solution, and yours worked fineWaterford
You are welcome! You can also find useful my second approach described in this answer: #24495862Illiterate
B
10

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

Bootle answered 22/6, 2017 at 8:55 Comment(10)
it is not working for me with Custom Share Extension UI (directly subclassed UIViewController) with xcode 9, swift 3 and iOS 10.3.1Sensibility
@KauthamMurugan, just checked, works fine on xcode 9, swift 4 and iOS 11.Bootle
Have you replace the SLComposeServiceViewController to UIViewControllerin the ShareViewController ? I think that should be the problem, but i am not sure.Sensibility
@KauthamMurugan, yes, I am subclassing UIViewController in ShareViewController. Not using SLComposeServiceViewController.Bootle
Can confirm this code not working on XCode 9.1 (gist: gist.github.com/broskoski/5de3da49de3ae9add1e0f71f6c6e08fd)Pasha
doesn't work for me. Mine is an ActionViewController: UIViewControllerZizith
@Zizith it still works for me. Probably you are doing something else wrong or you have different config somewhere.Bootle
If it doesn't work, probably you app is not supporting universal app links properly. Read here about here more: developer.apple.com/library/content/documentation/General/…Bootle
This code worked for me flawlessly in iMessage extension. Tested on iOS 13. I opened app store review URL with this code. Thank you!Mashie
I'm using Swift 5.4 with Xcode 12.5 and this code WORKED for me! Thanks @DenissFedotovsInquest
N
7

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.

Naidanaiditch answered 19/6, 2014 at 11:36 Comment(6)
Thank you for telling me that you occur the same problem. What do you mean for “dupe”? Do you mean I should add comment at your bug reports?Wolfson
Sorry for not being clear. You should report it at bugreport.apple.com and in Additional Notes field write: It's a duplicate of rdar://17376354. The more bug reports point to the issue, the better is the chance of Apple fixing it.Naidanaiditch
I managed to do the steps as you mentioned, haha. I also wrote to evangelist of Apple which is shown in the end of the WWDC extension video. Hope Apple could give us some response.Wolfson
Do you have any progress? The evangelist has not response to me.Wolfson
@LaurenceFan: Not yet. I guess we have to give them some more time.Naidanaiditch
Apple has changed its documentation: In some cases, it can make sense for a Today widget to request its containing app to open.Pretor
C
7

NSExtensionContext only support openURL function in today extension ,this is described in apple's documents about NSExtensionContext.The original words is "Each extension point determines whether to support this method, or under which conditions to support this method. In iOS 8.0, only the Today extension point supports this method."

Charged answered 16/10, 2014 at 4:26 Comment(0)
K
6

A possible workaround: Create and add a small UIWebView to your view and run it's method loadRequest with the url scheme you set above. This is a workaround and I'm not sure what Apple will say about it. Good luck!

Kerr answered 7/7, 2014 at 15:45 Comment(8)
nice to see this answer! My leader also suggest this workaround. I have try this and let the UIWebView run a javaScript to automatically simulate the "click the jump link" action. And I can do it successfully.Wolfson
It remind me that maybe Apple will not allow such behavior. As @IanBaird mentioned, it seems Apple really not approve it. And I voted your answer. But I think we can not resolve this problem now. Do you think so?Wolfson
@LaurenceFan - I am waiting for an official Apple response regarding this API validity - will update!Kerr
Hi @nurne, Do you see Ian Baird's answer? Do you think the answer is correct? Hope your thread will be answered by Apple staff or someone else.Wolfson
@LaurenceFan Has anyone submitted an app yet using this workaround? I'd be interested to know whether Apple accepted it.Drugstore
@MatthewGertner We do not use extension in the end. Since our manager think there are other things more important than implement the extension of our app. Thanks for asking the question.Wolfson
This functionality broke in iOS 8.3.Windowsill
We got it accepted in an app but i confirm @HankBrekke finding. It does not work since 8.3Dispel
G
6

An updated version of Julio Bailon's answer with modern Swift syntax:

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

There is no need for an extension for NSObject now.

Note: you must wait for the view to be attached to the view hierarchy before calling this code otherwise the responder chain can't be used.

Generation answered 27/10, 2015 at 11:3 Comment(2)
Thanks for this - I've got about 3 hours experience with Swift and iOS so this is really helpful :)Quizmaster
"Note: you must wait for the view to be attached to the view hierarchy before calling this code otherwise the responder chain can't be used." This is very important. I wasted a lot of time until I realized calling from viewDidLoad won't work but viewWillAppear did work.Manville
F
3

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)
        }
    }
Firebrick answered 18/3, 2017 at 21:13 Comment(2)
It does really work! Tested Xcode 8.3.1 + swift 3 + iPhone 5s + iOS 10.3.2Batangas
From share extension app click in iOS 13.1.3, it's not working. Do you have a fix ?Diablerie
G
2

A bit safer option using recursion

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
    }


Gunflint answered 17/8, 2020 at 8:51 Comment(0)
F
0

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.

Franzen answered 2/8, 2014 at 18:34 Comment(1)
Right but nowhere does it say what types support itGastropod
E
0

Only the Today Extension seems to work.
It's not 100% documented, but an apple employee specifically says that Keyboard extensions do not support openURL:completionHandler.
The documentation says:

Each extension point determines whether to support this method, or under which conditions to support this method.

So in practice, Share, Action, Keyboard, and Document provider do not work for anyone (beta 5) and only Today Extension supports it.

Exterminate answered 7/8, 2014 at 18:22 Comment(0)
G
0

As apple document " A Today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class."

For other Extension, I used this solution

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];
Gigolo answered 28/1, 2015 at 6:12 Comment(0)
E
-2

My guess is that this is intentionally not possible. The openURL:completionHandler: block says that it may not be supported in all extension types, and the action extension docs explicitly say:

If you want to help users share content on a social website or give users updates on information they care about, the Action extension point is not the right choice.

I think a share extension might be more appropriate, but the docs for both types suggest that the experience should be embedded in the host app, not taking the users to your app, so it might not allow that for that either. So, perhaps follow the share extension docs and just upload your image from within the extension UI as it suggests?

Exhalation answered 19/6, 2014 at 2:4 Comment(5)
I read Apple document again and could not found about "both types suggest that the experience should be embedded in the host app". Where did you saw it? Or could you kindly paste the original words in the Apple document and I can search them via Google? :)Wolfson
@LaurenceFan "When users choose your Share extension, you display a view in which they compose their content and post it" and "In iOS, an Action extension... Always appears in an action sheet or full-screen modal view". Note that neither say anything about launching the host app; it's certainly not explicit, though.Exhalation
I think I understand your meaning and I found such words in document. Thanks a lot. We need modify the image and do some business logic, so just use Share extension to upload the image is not enough. Do you think Apple will provide more explanation and examples?Wolfson
@LaurenceFan You should be able to modify the image and perform business logic within the share extension. If there's some reason you can't, you should file a bug with Apple (or edit your existing one) explaining clearly why you need to be able to use openURL for your use case.Exhalation
I try to use "shared resource" to transfer gallery's image to my own app. I will learn from your suggestions and may file a bug if it is necessary.Wolfson

© 2022 - 2024 — McMap. All rights reserved.