我要什么
在 Iphone 上,当在 Safari 或 Chrome 中访问网站时,可以将内容共享给其他应用程序。在这种情况下,您可以看到我可以将内容(基本上是 URL)共享到名为 Pocket 的应用程序。
有可能这样做吗?特别是 Cordova ?
最佳答案
编辑 :迟早一个简单的移动网站可能能够接收从 native 应用程序共享的内容。检查 Web Share Target协议(protocol)
我正在回答我自己的问题,因为我们终于成功地为 Cordova 应用程序实现了 iOS 共享扩展。
首先共享扩展系统仅适用于 iOS >= 8
然而,将它集成到 Cordova 项目中有点痛苦,因为没有特殊的 Cordova 配置可以这样做。在创建共享扩展时,Cordova 团队很难对 XCode xproj 文件进行逆向工程以添加共享扩展,因此将来可能也很难......
您有 2 个选择:
我们决定采用第二个选项,因为我们的扩展非常稳定,我们不会经常修改它。
手动创建共享扩展
非常重要 : 创建共享扩展名,以及
Action.js
通过 XCode 界面!它们必须在 xproj 文件中注册,否则它根本无法工作。 See通过 XCode 创建文件
要为 Cordova 应用程序创建共享扩展,您必须像任何 iOS developer would do 一样做.
您将在 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 Controller 中检索该页面上所需的数据(这里我想要 url 和标题)。
自定义 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>
请注意,我们注册了
Action.js
该 plist 文件中的文件。自定义 ShareViewController.swift
通常,您必须自己实现将在现有应用程序之上运行的 Swift View (对我而言是在浏览器应用程序之上)。
默认情况下, Controller 将提供您可以使用的默认 View ,您可以从那里向后端执行请求。 Here is an example从中我激励自己这样做。
但就我而言,我不是 iOS 开发人员,我希望当用户选择我的扩展程序时,它会打开我的应用程序而不是显示 iOS View 。所以我用了 custom URL scheme打开我的应用剪刀:
myAppScheme://openClipper?url=SomeUrl
这允许我在 HTML/JS 中设计我的剪刀,而不必创建 iOS View 。请注意,我为此使用了 hack,Apple 可能会禁止在 future 的 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://stackoverflow.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://stackoverflow.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 和标题。自动化流程
您必须创建共享扩展文件和
Action.js
文件通过 XCode 接口(interface)。然而,一旦它们被创建(并在 XCode 中被引用),你就可以用你自己的文件替换它们。因此,我们决定将上述文件放在一个文件夹 (
/cordova/ios-share-extension
) 中,并用它们覆盖默认的共享扩展文件。这并不理想,但我们使用的最低程序是:
cordova prepare ios
) /cordova/ios-share-extension
的内容至 cordova/platforms/ios/MyClipper
这样扩展就可以在 xproj 文件中正确注册,但您仍然可以对扩展进行版本控制。
编辑 2017 :使用cordova-ios@5.0.0设置所有这些可能会变得更容易,请参阅https://issues.apache.org/jira/browse/CB-10218
关于ios - Cordova : sharing browser URL to my iOS app (Clipper ios share extension),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33105698/