ios iphone safari cfbundledocumenttypes

Cordova: compartir la URL del navegador en mi aplicación iOS(extensión Clipper ios share)



iphone safari (5)

A continuación del comentario de actualización de iOS 10 de Aaron Rosen, este es el proceso para que funcione:

  1. En el código de la respuesta original de Sebastien Lorber, actualice la función doOpenUrl como sugiere Aaron. Reubicando aquí para mayor claridad:

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

  2. Siga el proceso descrito en la respuesta inicial para crear la extensión en Xcode

  3. Seleccione ShareViewController.swift en la carpeta de extensión
  4. Vaya a Edición> Convertir> A sintaxis Swift actual
  5. En la configuración de compilación de la extensión, cambie "Requerir solo API segura de extensión de aplicación" a NO.

Solo entonces la extensión funcionará.

Lo que quiero

En un iPhone, cuando visita un sitio web dentro de Safari o Chrome, es posible compartir contenido con otras aplicaciones. En este caso, puede ver que puedo compartir el contenido (básicamente la URL) a una aplicación llamada Pocket.

¿Es posible hacer eso? ¿Y específicamente con Córdoba?


Debería poder lograr su objetivo con mucho menos trabajo manual utilizando este complemento cordova . También funcionará en Android.


Esa es una buena y relevante pregunta.

Traté de utilizar el impresionante cordova-plugin-openwith de Jean-Christophe Hoelt pero me enfrenté a varios problemas. El complemento está destinado a recibir elementos compartidos de un tipo (por ejemplo, URL, texto o imagen), que se configura durante la instalación. Además, con su implementación actual, escribir una nota para compartir y seleccionar un receptor en una aplicación Cordova son dos pasos diferentes en un contexto diferente (nativo y cordobés), por lo que no me pareció una buena experiencia de usuario.

Hice estas y otras correcciones a este complemento y lo publiqué como un complemento separado: https://github.com/EternallLight/cordova-plugin-openwith-ios

Tenga en cuenta que solo funciona para iOS, no para Android.


doOpenUrl () anterior necesita actualizarse para funcionar en iOS 10. El siguiente código también funciona en versiones anteriores de 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 } }


Editar : tarde o temprano, un sitio web móvil simple probablemente podrá recibir contenido compartido de aplicaciones nativas. Verifique el protocolo Web Share Target

Estoy respondiendo mi propia pregunta, ya que finalmente logramos implementar iOS Share Extension para una aplicación Cordova.

Primero, el sistema Share Extension solo está disponible para iOS> = 8

Sin embargo, es un poco doloroso integrarlo en un proyecto de Cordova porque no hay una configuración especial de Cordova para hacerlo. Al crear una extensión para compartir, es difícil para el equipo de Cordova realizar ingeniería inversa del archivo XCode xproj para agregar una extensión para compartir, por lo que probablemente también sea difícil en el futuro ...

Tienes 2 opciones:

  • Versión de algunos de sus archivos de plataforma iOS (como el archivo xproj)
  • Incluya un procedimiento manual después de generar la plataforma iOS con cordova

Decidimos optar por la segunda opción, ya que nuestra extensión es bastante estable y no la modificaremos con frecuencia.

Crear la extensión de compartir manualmente

MUY IMPORTANTE : ¡cree la extensión para compartir y el Action.js la interfaz XCode! Tienen que estar registrados en el archivo xproj o no funcionará en absoluto. See

Crea los archivos a través de XCode

Para crear una extensión compartida para una aplicación Cordova, deberá hacer lo que haría cualquier desarrollador de iOS .

  • Abra la plataforma ios xproj en XCode
  • Archivo> Nuevo> Destino> Compartir extensión
  • Seleccione Swift como idioma (solo porque ObjC me parece desagradable)

Obtiene una nueva carpeta en XCode con algunos archivos que deberá personalizar.

También necesitará un archivo Action.js adicional en esa carpeta de extensión compartida. Cree un nuevo archivo vacío (a través de XCode!) Action.js

Manejar la extracción de datos del navegador

Pon en Action.js el siguiente código:

var Action = function() {}; Action.prototype = { run: function(parameters) { parameters.completionFunction({"url": document.URL, "title": document.title }); }, finalize: function(parameters) { } }; var ExtensionPreprocessingJS = new Action

Cuando se selecciona su extensión de compartir en la parte superior de un navegador (creo que solo funciona para Safari), este JS se ejecutará y le permitirá recuperar los datos que desea en esa página en su controlador Swift (aquí quiero la URL y el título).

Personalizar Info.plist

Ahora debe personalizar el archivo Info.plist para describir qué tipo de extensión de recurso compartido está creando y qué tipo de contenido puede compartir en su aplicación. En mi caso, principalmente quiero compartir URL, así que aquí hay una configuración que funciona para compartir URL de Chrome o Safari.

<?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>

Observe que registramos el archivo Action.js en ese archivo plist.

Personalice el ShareViewController.swift

Normalmente, tendría que implementar usted mismo las vistas rápidas que se ejecutarán sobre la aplicación existente (para mí, sobre la aplicación del navegador).

De forma predeterminada, el controlador proporcionará una vista predeterminada que puede usar, y puede realizar solicitudes a su back-end desde allí. Aquí hay un ejemplo del cual me inspiré para hacerlo.

Pero en mi caso, no soy un desarrollador de iOS y quiero que cuando el usuario seleccione mi extensión, abra mi aplicación en lugar de mostrar vistas de iOS. Así que utilicé un esquema de URL personalizado para abrir mi aplicación clipper: myAppScheme://openClipper?url=SomeUrl Esto me permite diseñar mi clipper en HTML / JS en lugar de tener que crear vistas de iOS.

Tenga en cuenta que uso un truco para eso, y Apple puede prohibir abrir su aplicación desde una extensión de compartir en futuras versiones de iOS. Sin embargo, este truco funciona actualmente para iOS 8.xy 9.0.

Aquí está el código. Funciona tanto para Chrome como para Safari en iOS.

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

Observe que hay 2 formas de cargar el Dictionary<String,String> . Esto se debe a que Chrome y Safari parecen proporcionar la URL y el título de la página de 2 maneras diferentes.

Automatizando el proceso

Debe crear los archivos Share Extension y Action.js través de la interfaz XCode. Sin embargo, una vez que se crean (y se hace referencia en XCode), puede reemplazarlos con sus propios archivos.

Por lo tanto, decidimos que versionaremos los archivos anteriores en una carpeta ( /cordova/ios-share-extension ) y anularemos los archivos de extensión de compartir predeterminados con ellos.

Esto no es ideal, pero el procedimiento mínimo que utilizamos es:

  • Construya la plataforma iOS de Cordova ( cordova prepare ios )
  • Abrir proyecto en XCode
  • Crear extensión compartida con (nombre del producto = "MyClipper", idioma = "Swift", nombre de la organización = "MyCompany")
  • En "MyClipper", cree un archivo vacío "Action.js"
  • Copie el contenido de /cordova/ios-share-extension a cordova/platforms/ios/MyClipper

De esta manera, la extensión está registrada correctamente en el archivo xproj pero aún tiene la capacidad de controlar la versión de su extensión.

Editar 2017 : esto puede ser más fácil de configurar todo eso con [email protected], consulte https://issues.apache.org/jira/browse/CB-10218