Cordova:将浏览器 URL 共享到我的 iOS 应用程序(Clipper ios 共享扩展)
Posted
技术标签:
【中文标题】Cordova:将浏览器 URL 共享到我的 iOS 应用程序(Clipper ios 共享扩展)【英文标题】:Cordova: sharing browser URL to my iOS app (Clipper ios share extension) 【发布时间】:2015-10-13 14:44:45 【问题描述】:我想要什么
在 Iphone 上,当访问 Safari 或 Chrome 中的网站时,可以将内容共享给其他应用程序。在这种情况下,您可以看到我可以将内容(基本上是 URL)分享到名为 Pocket 的应用程序。
有可能做到吗?尤其是 Cordova?
【问题讨论】:
嘿塞巴斯蒂安,成功了吗? @BurhanMughal 我已经回答了我自己的问题!这是可能的,我花了很长时间才弄清楚!希望对你有帮助 【参考方案1】:编辑:一个简单的移动网站迟早可能会接收到本地应用程序共享的内容。检查Web Share Target协议
我正在回答我自己的问题,因为我们终于成功地为 Cordova 应用程序实现了 ios 共享扩展。
首先共享扩展系统仅适用于 iOS >= 8
但是,将它集成到 Cordova 项目中有点痛苦,因为没有特殊的 Cordova 配置可以这样做。在创建共享扩展时,Cordova 团队很难对 XCode xproj 文件进行逆向工程以添加共享扩展,因此将来可能也很难......
你有两个选择:
版本化您的一些 iOS 平台文件(如 xproj 文件) 在使用 cordova 生成 iOS 平台后包含一个手动过程我们决定使用第二个选项,因为我们的扩展非常稳定,我们不会经常修改它。
手动创建共享扩展
非常重要:通过 XCode 界面创建共享扩展和Action.js
!它们必须在 xproj 文件中注册,否则根本不起作用。 See
通过 XCode 创建文件
要为 Cordova 应用程序创建共享扩展,您必须像任何iOS developer would do 一样。
在XCode上打开ios平台xproj 文件 > 新建 > 目标 > 共享扩展 选择 Swift 作为语言(只是因为 ObjC 对我来说似乎不愉快)您会在 XCode 中获得一个新文件夹,其中包含一些您必须自定义的文件。
您还需要该共享扩展文件夹中的额外Action.js
文件。创建一个新的空文件(通过 XCode!)Action.js
处理浏览器数据提取
输入Action.js
以下代码:
var Action = function() ;
Action.prototype =
run: function(parameters)
parameters.completionFunction("url": document.URL, "title": document.title );
,
finalize: function(parameters)
;
var ExtensionPreprocessingJS = new Action
当您在浏览器顶部选择共享扩展时(我认为它仅适用于 Safari),此 JS 将运行并允许您在 Swift 控制器中检索该页面上所需的数据(这里我想要网址和标题)。
自定义 Info.plist
现在您需要自定义Info.plist
文件来描述您正在创建什么样的共享扩展,以及您可以将什么样的内容共享到您的应用程序。就我而言,我主要想分享 url,所以这里有一个配置,可用于从 Chrome 或 Safari 分享 url。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>MyClipper</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionjavascriptPreprocessingFile</key>
<string>Action</string>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>
请注意,我们在该 plist 文件中注册了 Action.js
文件。
自定义 ShareViewController.swift
通常您必须自己实现 Swift 视图,这些视图将在现有应用程序之上运行(对我而言,在浏览器应用程序之上)。
默认情况下,控制器将提供一个您可以使用的默认视图,并且您可以从那里向您的后端执行请求。 Here is an example 我从中启发了自己这样做。
但就我而言,我不是 iOS 开发人员,我希望当用户选择我的扩展程序时,它会打开我的应用程序而不是显示 iOS 视图。所以我使用custom URL scheme 打开我的应用剪辑器:myAppScheme://openClipper?url=SomeUrl
这允许我用 html / JS 设计我的剪辑器,而不必创建 iOS 视图。
请注意,我为此使用了 hack,Apple 可能会禁止在未来的 iOS 版本中从共享扩展程序打开您的应用程序。但是,此 hack 目前适用于 iOS 8.x 和 9.0。
这里是代码。它适用于 iOS 上的 Chrome 和 Safari。
//
// ShareViewController.swift
// MyClipper
//
// Created by Sébastien Lorber on 15/10/2015.
//
//
import UIKit
import Social
import MobileCoreServices
@available(iOSApplicationExtension 8.0, *)
class ShareViewController: SLComposeServiceViewController
let contentTypeList = kUTTypePropertyList as String
let contentTypeTitle = "public.plain-text"
let contentTypeUrl = "public.url"
// We don't want to show the view actually
// as we directly open our app!
override func viewWillAppear(animated: Bool)
self.view.hidden = true
self.cancel()
self.doClipping()
// We directly forward all the values retrieved from Action.js to our app
private func doClipping()
self.loadJsExtensionValues dict in
let url = "myAppScheme://mobileclipper?" + self.dictionaryToQueryString(dict)
self.doOpenUrl(url)
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
private func dictionaryToQueryString(dict: Dictionary<String,String>) -> String
return dict.map( entry in
let value = entry.1
let valueEncoded = value.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
return entry.0 + "=" + valueEncoded!
).joinWithSeparator("&")
// See https://github.com/extendedmind/extendedmind/blob/master/frontend/cordova/app/platforms/ios/extmd-share/ShareViewController.swift
private func loadJsExtensionValues(f: Dictionary<String,String> -> Void)
let content = extensionContext!.inputItems[0] as! NSExtensionItem
if (self.hasAttachmentOfType(content, contentType: contentTypeList))
self.loadJsDictionnary(content) dict in
f(dict)
else
self.loadUTIDictionnary(content) dict in
// 2 Items should be in dict to launch clipper opening : url and title.
if (dict.count==2) f(dict)
private func hasAttachmentOfType(content: NSExtensionItem,contentType: String) -> Bool
for attachment in content.attachments as! [NSItemProvider]
if attachment.hasItemConformingToTypeIdentifier(contentType)
return true;
return false;
private func loadJsDictionnary(content: NSExtensionItem,f: Dictionary<String,String> -> Void)
for attachment in content.attachments as! [NSItemProvider]
if attachment.hasItemConformingToTypeIdentifier(contentTypeList)
attachment.loadItemForTypeIdentifier(contentTypeList, options: nil) data, error in
if ( error == nil && data != nil )
let jsDict = data as! NSDictionary
if let jsPreprocessingResults = jsDict[NSExtensionJavaScriptPreprocessingResultsKey]
let values = jsPreprocessingResults as! Dictionary<String,String>
f(values)
private func loadUTIDictionnary(content: NSExtensionItem,f: Dictionary<String,String> -> Void)
var dict = Dictionary<String, String>()
loadUTIString(content, utiKey: contentTypeUrl , handler: url_NSSecureCoding in
let url_NSurl = url_NSSecureCoding as! NSURL
let url_String = url_NSurl.absoluteString as String
dict["url"] = url_String
f(dict)
)
loadUTIString(content, utiKey: contentTypeTitle, handler: title_NSSecureCoding in
let title = title_NSSecureCoding as! String
dict["title"] = title
f(dict)
)
private func loadUTIString(content: NSExtensionItem,utiKey: String,handler: NSSecureCoding -> Void)
for attachment in content.attachments as! [NSItemProvider]
if attachment.hasItemConformingToTypeIdentifier(utiKey)
attachment.loadItemForTypeIdentifier(utiKey, options: nil, completionHandler: (data, error) -> Void in
if ( error == nil && data != nil )
handler(data!)
)
// See https://***.com/a/28037297/82609
// Works fine for iOS 8.x and 9.0 but may not work anymore in the future :(
private func doOpenUrl(url: String)
let urlNS = NSURL(string: url)!
var responder = self as UIResponder?
while (responder != nil)
if responder!.respondsToSelector(Selector("openURL:")) == true
responder!.callSelector(Selector("openURL:"), object: urlNS, delay: 0)
responder = responder!.nextResponder()
// See https://***.com/a/28037297/82609
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)
)
请注意,有两种方法可以加载 Dictionary<String,String>
。这是因为 Chrome 和 Safari 似乎以两种不同的方式提供页面的 url 和标题。
自动化流程
您必须通过 XCode 界面创建共享扩展文件和Action.js
文件。但是,一旦它们被创建(并在 XCode 中引用),您就可以将它们替换为您自己的文件。
因此我们决定将上述文件版本化到一个文件夹 (/cordova/ios-share-extension
) 中,并用它们覆盖默认的共享扩展文件。
这并不理想,但我们使用的最小程序是:
构建 Cordova iOS 平台 (cordova prepare ios
)
在 XCode 中打开项目
使用 (product name="MyClipper", language="Swift", organization name="MyCompany") 创建共享扩展
在“MyClipper”上,创建一个空文件“Action.js”
将/cordova/ios-share-extension
的内容复制到cordova/platforms/ios/MyClipper
这样,扩展在 xproj 文件中正确注册,但您仍然可以对扩展进行版本控制。
Edit 2017:使用 cordova-ios@5.0.0 设置所有这些可能会变得更容易,请参阅 https://issues.apache.org/jira/browse/CB-10218
【讨论】:
谢谢!很有帮助! 非常感谢,这是 SO 上唯一一篇与原始问题相关的有用信息。我需要以各种组合处理 URL+文本+图像,并为此创建了此答案的派生。在没有合适的 iOS 共享扩展插件的情况下,github.com/inshikos/cordova-ios-share-extension/blob/master/… 可能会帮助其他需要将内容共享到 Cordova 应用程序的解决方案。 感谢@WarrenKim,您的代码将来也会对我有所帮助:) 嘿@SebastienLorber!你的帖子似乎真的很有帮助:) 谢谢!我想问一件事:我做了你描述的所有事情,但在共享屏幕中仍然看不到我的应用程序的图标。你能指出它可能卡在哪里吗? @StanislavIegorov 我不知道。也许你没有在 XCode 中设置 target >= 7.0,或者错过了一步【参考方案2】:上面的 doOpenUrl() 需要更新才能在 iOS 10 上运行。以下代码也适用于旧版本的 iOS。
private func doOpenUrl(url: String)
let url = NSURL(string:url)
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
【讨论】:
如何做到这一点“上面的 doOpenUrl() 需要更新才能在 iOS 10 上运行。”?粘贴这些代码后,我有很多红点。【参考方案3】:使用此cordova plugin,您应该能够以更少的手动工作来实现您的目标。它也适用于 android。
【讨论】:
将您的插件与电容器 3 一起使用,它适用于图像和视频。但我无法从任何应用程序中获得text / URL
。我的具体目标是从 Instagram 获取 Post URL。【参考方案4】:
这是一个很好且仍然相关的问题。
我尝试使用 Jean-Christophe Hoelt 的出色 cordova-plugin-openwith,但遇到了几个问题。该插件旨在接收在安装期间配置的一种类型的共享项目(例如,URL、文本或图像)。此外,在当前的实现中,在 Cordova 应用程序中编写要共享的便笺和选择接收器是不同(本机和 Cordova)上下文中的两个不同步骤,因此对我来说这不是一个好的用户体验。
我对此插件进行了这些和其他更正,并将其作为单独的插件发布: https://github.com/EternallLight/cordova-plugin-openwith-ios
请注意,它仅适用于 iOS,不适用于 Android。
【讨论】:
【参考方案5】:跟进 Aaron Rosen 的 iOS 10 更新评论,以下是使其工作的过程:
在 Sebastien Lorber 的原始答案中的代码中,按照 Aaron 的建议更新 doOpenUrl 函数。为了清楚起见,在此处重新发布:
private func doOpenUrl(url: String)
let url = NSURL(string:url)
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
按照初始答案中概述的过程在 Xcode 中创建扩展
在扩展文件夹中选择 ShareViewController.swift 转到编辑 > 转换 > 到当前的 Swift 语法 在扩展构建设置中,将“仅需要应用扩展安全 API”切换为否。只有这样扩展才能工作。
【讨论】:
使用此解决方案,它可以在模拟器上运行,但不能在真实设备上运行。知道为什么吗? 无法仅根据此评论告诉您原因,但您可以尝试在 Safari 中调试 javascript:确保您的手机已插入并且 Safari 已启用开发者模式(首选项 -> 高级)。当您启动该应用程序时,您的手机将出现在 Safari 的开发人员菜单中,您可以检查 web 视图并在那里检查控制台是否有错误。希望你能得到相关信息。检查 Xcode 中的日志,它们也可以包含相关信息。还要确保扩展名已正确签名。 感谢您的回复。不幸的是,应用程序中似乎一切正常(safari/xcode 日志中没有错误),此外,我的自定义 url 从 safari 启动应用程序,但不是从图库。我继续调查! 好的,这并不奇怪。到目前为止,我们只处理了从 Safari 中打开 URL,因为太忙而没有时间做更多的工作。如果您了解如何从其他应用程序处理它们,我很想听听如何!以上是关于Cordova:将浏览器 URL 共享到我的 iOS 应用程序(Clipper ios 共享扩展)的主要内容,如果未能解决你的问题,请参考以下文章
iOS:我可以将 Safari 中的 URL 共享到我自己的应用程序吗?
如何从另一个应用程序将图像共享到我的 Cordova/PhoneGap 应用程序?