我想为我的应用创建Android Style共享功能。 我创建了一个共享扩展,当你在股票照片应用程序中选择图片并按下共享时,它会被调用。 现在我想将这些图片发送到主应用程序并在那里处理。 我现在的问题是:
答案 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 :)方法
喜欢openURL(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。
答案 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时,系统会在完成请求之前验证该请求。