22

Does any one know how to launch the parent app from the app extension's view controller?

I just want to launch the main app from its app extension.

pkamb
  • 33,281
  • 23
  • 160
  • 191
loganathan
  • 2,056
  • 2
  • 23
  • 34

9 Answers9

10

In the WWDC session Creating Extensions for iOS and OS X, Part 1 around the 22 minute mark the say to use the openURL:completionHandler: method from the UIViewController's extensionContext to open a custom URL scheme

[self.extensionContext openURL:[NSURL URLWithString:@"your-app-custom-url-scheme://your-internal-url"]
             completionHandler:nil];
Karl Monaghan
  • 880
  • 9
  • 14
10

Working solution in Swift 3.1 (tested in iOS10):

You need to create your own URL Scheme for your host app, then add this function to your ViewController and call it with openURL("myScheme://myIdentifier")

//  Function must be named exactly like this so a selector can be found by the compiler!
//  Anyway - it's another selector in another instance that would be "performed" instead.
func openURL(_ url: URL) -> Bool {
    var responder: UIResponder? = self
    while responder != nil {
        if let application = responder as? UIApplication {
            return application.perform(#selector(openURL(_:)), with: url) != nil
        }
        responder = responder?.next
    }
    return false
}
coyer
  • 4,122
  • 3
  • 28
  • 35
  • PLzz tell how it will be called via extension as this seems incomplete answer – Abhishek Thapliyal Aug 26 '17 at 06:30
  • 1
    There is really nothing special. In my case I have a shareextension consisting of only one ViewController. I put `openURL` inside that ViewController and just called `self.openURL(URL(string: "myapp://com.mydomain.share?urlparams")!)` after viewDidAppear. The ViewController itself is nearly empty - I just collect incoming files and save them into a local folder before I do the call above. Do not forget to register your URL scheme `myapp`... – coyer Aug 28 '17 at 12:44
7

it is working in my current working app using action extension

1- in parent app plist add custom URL like enter image description here

2- add both functions in your extension view controller

func openURL(url: NSURL) -> Bool {
    do {
        let application = try self.sharedApplication()
        return application.performSelector(inBackground: "openURL:", with: 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?.next
    }

    throw NSError(domain: "UIInputViewController+sharedApplication.swift", code: 1, userInfo: nil)
}

3- call this function in your button action or somewhere you want to do

self.openURL(url: NSURL(string:"openPdf://HomeVC")!)

here homevc is the class name of your View controller which should be presented

4- in your app delegate implement the method like

  func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    let urlPath : String = url.absoluteString
    print(urlPath)
    if urlPath.contains("HomeVC"){
        //here go to firstViewController view controller
        self.window = UIWindow(frame: UIScreen.main.bounds)

        let storyboard = UIStoryboard(name: "Main", bundle: nil)

        let initialViewController = storyboard.instantiateViewController(withIdentifier: "homeVC")

        self.window?.rootViewController = initialViewController
        self.window?.makeKeyAndVisible()
    }
}

I hope will work fine, can also share data between extension app and parent app

Awais Mobeen
  • 733
  • 11
  • 19
  • For some reason it launches app and works without step 4. Moreover none of `AppDelegate` methods `func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool` or `func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool` fire. – Paul B Jul 08 '20 at 17:27
  • 1
    step 4 will be used when you need to open the vc other then initail vc – Awais Mobeen Jul 09 '20 at 04:51
  • Is there a reason for adding `URL Type` to `Info.plist`, not via project settings -> host target -> Info tab -> add URL Type , @Awais? – Paul B Jul 09 '20 at 15:42
  • This saved my day. Thanks – Cihan Kalmaz Aug 24 '23 at 21:16
2

I asked a similar question here: Communicating with/opening Containing app from Share extension. Basically, you can do a couple things.

  1. Use a UIWebView's loadRequest webView to load an NSURL request with your containing app's url. For example,

    NSURL *url = [NSURL URLWithString:@"myurl"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [self.webView loadRequest:request];
    
  2. Use a UIDocumentInteractionController and a custom file extension type to provide a link to your containing app (and your containing app only)

  3. Start a "fake" NSURL session to get the following functionality: In iOS, if your extension isn’t running when a background task completes, the system launches your containing app in the background and calls the application:handleEventsForBackgroundURLSession:completionHandler: app delegate method.

The first one is probably your best bet.

Community
  • 1
  • 1
arcticmatt
  • 1,956
  • 1
  • 19
  • 36
  • but apple will recommend this way?. Please confirm – loganathan Sep 10 '14 at 06:59
  • @loganathan not really sure, but personally I think its clean enough that Apple will let it slide (especially based on some of the stuff they've let into the app store). – arcticmatt Sep 10 '14 at 17:45
  • 1
    FWIW: I used this method and it worked in iOS 8.0 for sure, but at some point between then and now (perhaps 9.0, perhaps earlier), it stopped working :( – radex Jul 05 '16 at 08:28
2

The documentation is pretty clear on saying that you can use extensionContext openURL within a Today extension. By implication, openURL can only be used in a Today exception and our experiences are proving this to be true--I also can't get it working from within a Share extension.

I would be interested to see if any of these work arounds are accepted by Apple's review process. I doubt it. If Apple wanted it to work, it would have been easy enough for them to allow it.

Apple allows any Today widget to use the openURL:completionHandler: method to open the widget’s own containing app.

If you employ this method to open other apps from your Today widget, your App Store submission might entail additional review to ensure compliance with the intent of Today widgets.

I'm certain this second paragraph was added after Launcher was accepted, rejected, and then later approved for sale.

Dan Loughney
  • 4,647
  • 3
  • 25
  • 40
2

Here is another way to do it:

  1. Step 1 from here
  2. Select extension target and go to its Build Settings. Set Require Only App-Extension Safe API to NO.
  3. Use UIApplication.shared.openURL(URL(string:"openPdf://")!) as you normally would do it outside extension to open urls.

Note, that you'll get 'openURL' was deprecated in iOS 10.0 warning. So this looks like it is not guaranteed to work in future.

Consider using local Notifications to wake up host app instead of this.

Paul B
  • 3,989
  • 33
  • 46
  • UIApplication.shared.open(URL(string:"openPdf://")!) instead of UIApplication.shared.openURL(URL(string:"openPdf://")!) – Fadi Abuzant Jul 12 '20 at 12:23
  • it works! no need to run across responders anymore – djdance Apr 09 '21 at 13:39
  • I get this issue : Application extensions and any libraries they link to must be built with the `APPLICATION_EXTENSION_API_ONLY` build setting set to YES. – jontelang Jan 27 '23 at 06:47
  • Don't have time to check it now, @jontelang. Deprecations are possible. Did you try to set the build settings as the error message advises? – Paul B Jan 28 '23 at 11:00
  • Yeah, the problem is that the extension needs it to use the API that this answer is about. I guess it worked before, I think it no longer works though. – jontelang Jan 28 '23 at 11:23
2

Here is Valentin Shergin's solution for ios 14 & xcode 13

extension KeyboardViewController{
    func openURL(url: NSURL) -> Bool {
            do {
                let application = try self.sharedApplication()
                return (application.perform("openURL:", with: 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?.next
            }

            throw NSError(domain: "UIInputViewController+sharedApplication.swift", code: 1, userInfo: nil)
        }
}
Muhammad Ahmad
  • 252
  • 3
  • 13
1

Here is working solution (tested on iOS 9.2) at least 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)
    }

}
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
1

I use this code in my extension to open the main app with a custom scheme url:

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

Make sure that the extension gets enough time to load before calling this function.

Ely
  • 8,259
  • 1
  • 54
  • 67