Share Extension to open containing app

半世苍凉 提交于 2019-12-17 09:33:45

问题


I want to create an Android Style share feature for my app. I created a share extension which gets called when you select pictures inside the stock photo app and press share. Now I want those pictures to be sent to the main app and get handled over there. My question is now:

  1. Can iOS open my app after a button is pressed on the share extension window?
  2. How do I get the picture files inside my main app?

回答1:


Currently there's no way to do this. A share extension cannot open the containing app.

The intended approach for share extensions is that they handle all of the necessary work themselves. Extensions can share code with their containing apps by using custom frameworks, so in most cases that's no problem.

If you want to make data available to your app, you can set up an app group so that you have a shared directory. The extension can write data there, and the app can read it. That won't happen until the next time the user launches the app, though.




回答2:


Swift 4+ (tested on iOS 13)

@objc should be added to the declaration of openURL, that is,

@objc func openURL(_ url: URL) -> Bool {
    // Code below.
}

Without it one would see this compiler error:

Argument of '#selector' refers to instance method 'openURL' that is not exposed to Objective-C

Working solution in Swift 3.1 (tested in iOS10):

You need to create your own URL Scheme, 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
}

Edit: Notes for clarification: openURL is a method of UIApplication - since your ShareExtension is not derived from UIApplication I added my own openURL with the same definition as the one from UIApplication to keep the compiler happy (so that #selector(openURL(_:) can be found).

Then I go through the responders until I find one that is really derived from UIApplication and call openURL on that.

More stripped-down-example-code which copies files in a ShareExtension to a local directory, serializing filenames and calling openURL on another app:

//
//  ShareViewController.swift
//

import UIKit
import Social
import MobileCoreServices

class ShareViewController: UIViewController {

var docPath = ""

override func viewDidLoad() {
    super.viewDidLoad()

    let containerURL = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.com.my-domain")!
    docPath = "\(containerURL.path)/share"

    //  Create directory if not exists
    do {
        try FileManager.default.createDirectory(atPath: docPath, withIntermediateDirectories: true, attributes: nil)
    } catch let error as NSError {
        print("Could not create the directory \(error)")
    } catch {
        fatalError()
    }

    //  removing previous stored files
    let files = try! FileManager.default.contentsOfDirectory(atPath: docPath)
    for file in files {
        try? FileManager.default.removeItem(at: URL(fileURLWithPath: "\(docPath)/\(file)"))
    }
}

override func viewDidAppear(_ animated: Bool) {

    let alertView = UIAlertController(title: "Export", message: " ", preferredStyle: .alert)

    self.present(alertView, animated: true, completion: {

        let group = DispatchGroup()

        NSLog("inputItems: \(self.extensionContext!.inputItems.count)")

            for item: Any in self.extensionContext!.inputItems {

            let inputItem = item as! NSExtensionItem

            for provider: Any in inputItem.attachments! {

                let itemProvider = provider as! NSItemProvider
                group.enter()
                itemProvider.loadItem(forTypeIdentifier: kUTTypeData as String, options: nil) { data, error in
                    if error == nil {
                        //  Note: "data" may be another type (e.g. Data or UIImage). Casting to URL may fail. Better use switch-statement for other types.
                        //  "screenshot-tool" from iOS11 will give you an UIImage here
                        let url = data as! URL
                        let path = "\(self.docPath)/\(url.pathComponents.last ?? "")"
                        print(">>> sharepath: \(String(describing: url.path))")

                        try? FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: path))

                    } else {
                        NSLog("\(error)")
                    }
                    group.leave()
                }
            }
        }

        group.notify(queue: DispatchQueue.main) {
            NSLog("done")

            let files = try! FileManager.default.contentsOfDirectory(atPath: self.docPath)

            NSLog("directory: \(files)")

            //  Serialize filenames, call openURL:
            do {
                let jsonData : Data = try JSONSerialization.data(
                    withJSONObject: [
                        "action" : "incoming-files"
                        ],
                    options: JSONSerialization.WritingOptions.init(rawValue: 0))
                let jsonString = (NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
                let result = self.openURL(URL(string: "myapp://com.myapp.share?\(jsonString!)")!)
            } catch {
                alertView.message = "Error: \(error.localizedDescription)"
            }
            self.dismiss(animated: false) {
                self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
            }
        }
    })
}

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



回答3:


Technically you can't open containing app from share extension, but you can schedule local notification, and that's what I end up doing. Just before I call super.didSelectPost, I schedule local notification with some text, and if user wants to open containing app, they can, and if not - they can continue with their workflow. I even think its a better approach than automatically opening containing app and disrupting what they are doing.




回答4:


I opened the host app from shared extension with a trick. Using a webview with clear background color. below is the code

 NSString *customURL = @"MY_HOST_URL_SCHEME_APP://";
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)];
webView.backgroundColor = [UIColor clearColor];
    webView.tintColor = [UIColor clearColor];
    [webView setOpaque:NO];
    [self.view addSubview:webView];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:customURL]];
    [webView loadRequest:urlRequest];
    [self didSelectCancel];



回答5:


Implement custom url schema in host app and call openURL(url:) method

like openURL(url:NSURL(string:"schema_name://"))

extension SLComposeServiceViewController {

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

}



回答6:


I was having this problem, and in iOS 11+ none of the previous answers work. I ended up adding a completion handler to my JavaScript code, and from there setting window.location="myapp://". It's a bit hacky but it doesn't look to bad and the user can follow along.




回答7:


Not only there is no way (and won't be) to do this: there is no NEED to handle this in the app. The extension is supposed to handle this with the very same codebase as the main app. You should create a framework with extension safe API shared between the app and the extesnion targets.

This is the top topic here: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1




回答8:


EDIT: This solution works for today extension (Widget).

An extension can open the hosting app:

- (IBAction)launchHostingApp:(id)sender
{
 NSURL *pjURL = [NSURL URLWithString:@"hostingapp://home"];
[self.extensionContext openURL:pjURL completionHandler:nil];
}

And like Apple says in Handling Commons Scenarios :

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.



来源:https://stackoverflow.com/questions/27506413/share-extension-to-open-containing-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!