共享扩展以打开包含应用程序

时间:2014-12-16 14:02:26

标签: ios cocoa-touch ios8-share-extension

我想为我的应用创建Android Style共享功能。 我创建了一个共享扩展,当你在股票照片应用程序中选择图片并按下共享时,它会被调用。 现在我想将这些图片发送到主应用程序并在那里处理。 我现在的问题是:

  1. 在共享扩展程序窗口按下按钮后iOS可以打开我的应用程序吗?
  2. 如何在主应用程序中获取图片文件?

10 个答案:

答案 0 :(得分:23)

Swift 3.1中的工作解决方案(在iOS10中测试):

您需要创建自己的网址,然后将此功能添加到ViewController并使用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
}

编辑:澄清说明: openURL是UIApplication的一种方法 - 因为您的ShareExtension不是从UIApplication派生的,所以我添加了自己的openURL,其定义与UIApplication中的相同,以保持编译器满意(以便 #selector (可以找到openURL(_:)

然后我通过响应者,直到找到一个真正来自UIApplication并在其上调用openURL的响应者。

更精简的示例代码,它将ShareExtension中的文件复制到本地目录,序列化文件名并在另一个应用程序上调用openURL:

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

答案 1 :(得分:8)

目前无法做到这一点。共享扩展程序无法打开包含的应用。

共享扩展的预期方法是他们自己处理所有必要的工作。扩展程序可以使用自定义框架与其包含的应用程序共享代码,因此在大多数情况下这没有问题。

如果您想为您的应用提供数据,您可以设置一个应用组,以便拥有一个共享目录。扩展可以在那里写入数据,应用程序可以读取它。但是,直到下次用户启动应用程序时才会发生这种情况。

答案 2 :(得分:3)

我通过一个技巧从共享扩展程序打开了主机应用程序。 使用具有清晰背景颜色的webview。 下面是代码

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

答案 3 :(得分:2)

在主机应用中实现自定义网址架构并调用openURL(url :)方法

  

喜欢o​​penURL(url:NSURL(字符串:" 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)
    }

}

答案 4 :(得分:1)

从技术上讲,您无法从共享扩展中打开包含应用程序的内容,但可以安排本地通知,这就是我最终要做的事情。就在我致电super.didSelectPost之前,我计划了一些文本的本地通知,并且如果用户要打开包含应用程序的内容,则可以(如果不能)-他们可以继续其工作流程。我什至认为,这比自动打开包含应用程序并破坏他们正在做的事情更好。

答案 5 :(得分:1)

Xamarin.iOS 版本的@coyer答案:

using System;
using Foundation;
using UIKit;
using MobileCoreServices;
using CoreFoundation;
using System.Linq;
using Newtonsoft.Json;
using System.Collections.Generic;
using ObjCRuntime;
using System.Runtime.InteropServices;

namespace Your.ShareExtension
{
public partial class ShareViewController : UIViewController
{
    public ShareViewController(IntPtr handle) : base(handle)
    {
    }

    string docPath = "";

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        try
        {
            var containerURL = new NSFileManager().GetContainerUrl("group.com.qsiga.startbss");
            docPath = $"{containerURL.Path}/share";

            //  Create directory if not exists
            try
            {
                NSFileManager.DefaultManager.CreateDirectory(docPath, true, null);
            }
            catch (Exception e)
            { }

            //  removing previous stored files
            NSError contentError;
            var files = NSFileManager.DefaultManager.GetDirectoryContent(docPath, out contentError);
            foreach (var file in files)
            {
                try
                {
                    NSError err;
                    NSFileManager.DefaultManager.Remove($"{docPath}/{file}", out err);
                }
                catch (Exception e)
                { }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("ShareViewController exception: " + e);
        }
    }

    public override void ViewDidAppear(bool animated)
    {
        var alertView = UIAlertController.Create("Export", " ", UIAlertControllerStyle.Alert);

        PresentViewController(alertView, true, () =>
        {

            var group = new DispatchGroup();

            foreach (var item in ExtensionContext.InputItems)
            {

                var inputItem = item as NSExtensionItem;

                foreach (var provider in inputItem.Attachments)
                {

                    var itemProvider = provider as NSItemProvider;
                    group.Enter();
                    itemProvider.LoadItem(UTType.Data.ToString(), null, (data, error) =>
                                {
                                    if (error == null)
                                    {
                                        //  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
                                        var url = data as NSUrl;
                                        var path = $"{docPath}/{(url.PathComponents.LastOrDefault() ?? "")}";

                                        NSError err;
                                        NSFileManager.DefaultManager.Copy(url, NSUrl.CreateFileUrl(path, null), out err);
                                    }
                                    group.Leave();
                                });
                }
            }

            group.Notify(DispatchQueue.MainQueue, () =>
            {
                try
                {
                    var jsonData = JsonConvert.SerializeObject(new Dictionary<string, string>() { { "action", "incoming-files" } });
                    var jsonString = NSString.FromData(jsonData, NSStringEncoding.UTF8).CreateStringByAddingPercentEncoding(NSUrlUtilities_NSCharacterSet.UrlQueryAllowedCharacterSet);
                    var result = openURL(new NSUrl($"startbss://share?{jsonString}"));
                }
                catch (Exception e)
                {
                    alertView.Message = $"Error: {e.Message}";
                }
                DismissViewController(false, () =>
                {
                    ExtensionContext?.CompleteRequest(new NSExtensionItem[] { }, null);
                });
            });
        });
    }

    public bool openURL(NSUrl url)
    {
        UIResponder responder = this;
        while (responder != null)
        {
            var application = responder as UIApplication;
            if (application != null)
                return CallSelector(application, url);

            responder = responder?.NextResponder;
        }
        return false;
    }

    [DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
    static extern bool _callSelector(
        IntPtr target,
        IntPtr selector,
        IntPtr url,
        IntPtr options,
        IntPtr completionHandler
    );

    private bool CallSelector(UIApplication application, NSUrl url)
    {
        Selector selector = new Selector("openURL:options:completionHandler:");

        return _callSelector(
            application.Handle,
            selector.Handle,
            url.Handle,
            IntPtr.Zero,
            IntPtr.Zero
        );
    }
}
}

答案 6 :(得分:0)

我遇到了这个问题,在iOS 11+中,以前的答案都没有。我最终在我的JavaScript代码中添加了一个完成处理程序,并从那里设置window.location="myapp://"。这有点苛刻,但它看起来并不坏,用户可以跟进。

答案 7 :(得分:0)

我可以通过访问shared UIApplication instance via key-value coding并在其上调用openURL来使其工作:

let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as! UIApplication

let selector = NSSelectorFromString("openURL:")

let url = URL(string: "jptest://")!

application.perform(selector, with: url)

答案 8 :(得分:-1)

不仅没有办法(并且不会成为)这样做: 没有必要在应用程序中处理此问题。 扩展应该用这个来处理 与主应用程序相同的代码库。你应该创建一个框架 在应用程序和extesnion目标之间共享扩展安全API。

这是这里的首要话题: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1

答案 9 :(得分:-6)

编辑:此解决方案适用于今天的扩展(Widget)。

扩展程序可以打开托管应用程序:

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

就像Apple在Handling Commons Scenarios中所说:

扩展程序不直接告诉其包含的应用程序打开;相反,它使用NSExtensionContext的openURL:completionHandler:方法告诉系统打开其包含的应用程序。当扩展程序使用此方法打开URL时,系统会在完成请求之前验证该请求。