ios - extension - La extensión openURL no funciona en acción
cual es la extensión de ios (16)
Agrego el siguiente código:
- (IBAction)done {
// Return any edited content to the host app.
// This template doesn''t do anything, so we just echo the passed in items.
NSURL *url = [NSURL URLWithString:@"lister://today"];
[self.extensionContext openURL:url completionHandler:^(BOOL success) {
NSLog(@"fun=%s after completion. success=%d", __func__, success);
}];
[self.extensionContext completeRequestReturningItems:self.extensionContext.inputItems completionHandler:nil];
}
después de crear el objetivo de extensión de acción. Pero no puede funcionar.
Mi propósito es que: cuando el usuario vea una foto en Photos.app (la Galería de fotos o fotos por defecto de iOS), y haga clic en el botón Compartir para iniciar nuestra vista de extensión. Podemos transferir la imagen de Photos.app a mi propia aplicación y tratar o cargar la imagen en mi aplicación.
También pruebo "CFBundleDocumentTypes" pero tampoco funciona.
Cualquier ayuda será apreciada.
Apple aceptó la siguiente solución, que es el "mismo" código que usaría una aplicación host. Funciona en todas las versiones de iOS 8 hasta la fecha (probado en iOS 8.0 - iOS 8.3).
NSURL *destinationURL = [NSURL URLWithString:@"myapp://"];
// Get "UIApplication" class name through ASCII Character codes.
NSString *className = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x55, 0x49, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E} length:13] encoding:NSASCIIStringEncoding];
if (NSClassFromString(className)) {
id object = [NSClassFromString(className) performSelector:@selector(sharedApplication)];
[object performSelector:@selector(openURL:) withObject:destinationURL];
}
Como documento de Apple, el widget "A Today" (y ningún otro tipo de extensión de aplicación) puede solicitar al sistema que abra su aplicación que contiene llamando al método openURL: completionHandler: de la clase NSExtensionContext. "
Para otra extensión, utilicé esta solución
UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"ownApp://"; // Use other application url schema.
NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv=''refresh'' content=''0; URL=%@''></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:1.0];
El siguiente código funciona en Xcode 8.3.3, iOS10, Swift3 y Xcode 9, iOS11, Swift4 sin ningún tipo de advertencias del compilador:
func openUrl(url: URL?) {
let selector = sel_registerName("openURL:")
var responder = self as UIResponder?
while let r = responder, !r.responds(to: selector) {
responder = r.next
}
_ = responder?.perform(selector, with: url)
}
func canOpenUrl(url: URL?) -> Bool {
let selector = sel_registerName("canOpenURL:")
var responder = self as UIResponder?
while let r = responder, !r.responds(to: selector) {
responder = r.next
}
return (responder!.perform(selector, with: url) != nil)
}
Asegúrate de que tu aplicación sea compatible con Universal Links; de lo contrario, abrirá el enlace en el navegador. Más información aquí: https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html
Esto es lo que solía hacer:
UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
NSString *urlString = @"https://itunes.apple.com/us/app/watuu/id304697459";
NSString * content = [NSString stringWithFormat : @"<head><meta http-equiv=''refresh'' content=''0; URL=%@''></head>", urlString];
[webView loadHTMLString:content baseURL:nil];
[self.view addSubview:webView];
[webView performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:2.0];
Tenga en cuenta que en este caso estoy instanciando esta llamada desde el UIInputViewController.
Este método también debería funcionar usando el esquema de URL de la aplicación que lo contiene
ACTUALIZACIÓN 17/04/2015: Esto no funciona con iOS 8.3. Estamos buscando una solución y actualizaremos la respuesta pronto
ACTUALIZACIÓN 01/06/2015: encontramos una solución que funciona en iOS 8.3
var responder = self as UIResponder?
while (responder != nil){
if responder!.respondsToSelector(Selector("openURL:")) == true{
responder!.callSelector(Selector("openURL:"), object: url, delay: 0)
}
responder = responder!.nextResponder()
}
Esto encontrará un respondedor adecuado para enviar el OpenURL a.
Necesita agregar esta extensión que reemplaza el selector de rendimiento para swift y ayuda en la construcción del mecanismo:
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)
})
}
}
ACTUALIZACIÓN 15/06/2015: Objective-C
Alguien pidió el código en Objective-C así que aquí está. No voy a ejecutarlo ya que no tengo tiempo en este momento, pero debería ser bastante sencillo:
UIResponder *responder = self;
while(responder){
if ([responder respondsToSelector: @selector(OpenURL:)]){
[responder performSelector: @selector(OpenURL:) withObject: [NSURL URLWithString:@"www.google.com" ]];
}
responder = [responder nextResponder];
}
Como mencioné, no he ejecutado este código Objective-C, es solo una conversión del código Swift. Por favor, avíseme si encuentra algún problema y la solución y la actualizaré. Hoy en día, solo uso velozmente y desafortunadamente mi cerebro está desaprobando Objective-C
ACTUALIZACIÓN 05/02/2016: funciones obsoletas
Como señala @KyleKIM, las funciones de Selector han sido reemplazadas en Swift 2.2 por #selector. Además, hay una función que está en desuso y probablemente se eliminará en Swift 3.0, así que estoy investigando para encontrar una alternativa.
ACTUALIZACIÓN 16/09/2016: XCode 8, Swift 3.0 e iOS10 El siguiente código todavía está trabajando en las versiones mencionadas. Recibirás algunas advertencias:
let url = NSURL(string:urlString)
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
}
ACTUALIZACIÓN 15/06/2017: XCode 8.3.3
let url = NSURL(string: urlString)
let selectorOpenURL = sel_registerName("openURL:")
let context = NSExtensionContext()
context.open(url! as URL, completionHandler: nil)
var responder = self as UIResponder?
while (responder != nil){
if responder?.responds(to: selectorOpenURL) == true{
responder?.perform(selectorOpenURL, with: url)
}
responder = responder!.next
}
Esto es por diseño. No deseamos que las acciones personalizadas se conviertan en iniciadores de aplicaciones.
Mi suposición es que esto intencionalmente no es posible. El openURL:completionHandler:
dice que puede que no sea compatible con todos los tipos de extensión, y los documentos de extensión de acción dicen explícitamente:
Si desea ayudar a los usuarios a compartir contenido en un sitio web social o a actualizar a los usuarios sobre la información que les importa, el punto de extensión de Acción no es la opción correcta.
Creo que una extensión compartida podría ser más apropiada, pero los documentos para ambos tipos sugieren que la experiencia debe estar integrada en la aplicación host, sin llevar a los usuarios a su aplicación, por lo que tampoco podría permitir eso. Entonces, ¿quizás sigas los documentos de extensión para compartir y solo subas tu imagen desde la interfaz de usuario de extensión como sugiere?
NSExtensionContext solo admite la función openURL en la extensión de hoy, esto se describe en los documentos de apple sobre NSExtensionContext. Las palabras originales son "Cada punto de extensión determina si se admite este método o bajo qué condiciones se admite este método. En iOS 8.0, solo la extensión Today punto apoya este método ".
No todos los tipos de extensiones de aplicaciones son compatibles con "extensionContext openURL".
Probé en iOS 8 beta 4 y encontré que la extensión Today lo admite, pero la extensión de teclado no.
Parece ser un error, porque los documentos dicen:
Abrir la aplicación contenedora
En algunos casos, puede tener sentido que una extensión solicite que se abra su aplicación contenedora. Por ejemplo, el widget Calendario en OS X abre Calendario cuando los usuarios hacen clic en un evento. Para asegurarse de que su aplicación que contiene se abre de manera que tenga sentido en el contexto de la tarea actual del usuario, debe definir un esquema de URL personalizado que la aplicación y sus extensiones puedan usar.
Una extensión no indica directamente a su aplicación contenedora que se abra; en su lugar, utiliza el método openURL: completionHandler: NSExtensionContext para indicarle al sistema que abra su aplicación que lo contiene. Cuando una extensión utiliza este método para abrir una URL, el sistema valida la solicitud antes de cumplirla.
Lo reporté hoy: http://openradar.appspot.com/17376354 Deberías engañarte, si tienes algo de tiempo libre.
Prueba este código
UIResponder* responder = self;
while ((responder = [responder nextResponder]) != nil)
{
NSLog(@"responder = %@", responder);
if([responder respondsToSelector:@selector(openURL:)] == YES)
{
[responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:urlString]];
}
}
Solo la extensión Today parece funcionar.
No está 100% documentado, pero un empleado de Apple dice específicamente que las extensiones de teclado no son compatibles con openURL: completionHandler.
La documentación dice:
Cada punto de extensión determina si se admite este método o bajo qué condiciones se admite este método.
En la práctica, Share, Action, Keyboard y Document provider no funcionan para nadie (beta 5) y solo Today Extension lo admite.
Solución para el último iOS SDK 10.2. Todas las soluciones anteriores usan una API obsoleta. Esta solución se basa en la búsqueda de UIApplication UIResponder de la aplicación de alojamiento (esta aplicación que crea el contexto de ejecución para nuestra extensión). La solución solo se puede proporcionar en Objective-C porque hay un método de 3 argumentos para invocar y esto es imposible de hacer con performSelector:
methods. Para invocar este método no obsoleto, openURL:options:completionHandler:
necesitamos usar la instancia de NSInvocation que no está disponible en Swift. La solución proporcionada se puede invocar desde Objective-C y Swift (cualquier versión). Debo decir que aún no sé si la solución provista será válida para el proceso de revisión de Apple.
UIViewController+OpenURL.h
#import <UIKit/UIKit.h>
@interface UIViewController (OpenURL)
- (void)openURL:(nonnull NSURL *)url;
@end
UIViewController+OpenURL.m
#import "UIViewController+OpenURL.h"
@implementation UIViewController (OpenURL)
- (void)openURL:(nonnull NSURL *)url {
SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:");
UIResponder* responder = self;
while ((responder = [responder nextResponder]) != nil) {
NSLog(@"responder = %@", responder);
if([responder respondsToSelector:selector] == true) {
NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
// Arguments
NSDictionary<NSString *, id> *options = [NSDictionary dictionary];
void (^completion)(BOOL success) = ^void(BOOL success) {
NSLog(@"Completions block: %i", success);
};
[invocation setTarget: responder];
[invocation setSelector: selector];
[invocation setArgument: &url atIndex: 2];
[invocation setArgument: &options atIndex:3];
[invocation setArgument: &completion atIndex: 4];
[invocation invoke];
break;
}
}
}
@end
Desde Swift 3 Puede ejecutar esto solo si su controlador de vista está en jerarquía de vista. Este es el código de cómo lo estoy usando:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let context = self.extensionContext!
let userAuthenticated = self.isUserAuthenticated()
if !userAuthenticated {
let alert = UIAlertController(title: "Error", message: "User not logged in", preferredStyle: .alert)
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
context.completeRequest(returningItems: nil, completionHandler: nil)
}
let login = UIAlertAction(title: "Log In", style: .default, handler: { _ in
//self.openContainingAppForAuthorization()
let url = URL(string: "fashionapp://login")!
self.open(url)
context.completeRequest(returningItems: nil, completionHandler: nil)
})
alert.addAction(cancel)
alert.addAction(login)
present(alert, animated: true, completion: nil)
}
}
Solución trabajada en Swift 3.0 y 4.0:
// For skip compile error.
func openURL(_ url: URL) {
return
}
func openContainerApp() {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: "containerapp://")!)
return
}
responder = responder?.next
}
}
Explicación:
En extensión, api está limitado por el compilador para que no le permita usar openURl (: URL) como en la aplicación contenedor. Sin embargo, la API todavía está aquí.
Y no podemos realizar el método en nuestra clase hasta que lo declaremos, lo que realmente queremos es dejar que UIApplication realice este método.
Llamar a la cadena de respuesta, podemos usar
var responder: UIResponder? = self as UIResponder
responder = responder?.next
para ir al objeto UIApplication.
Y mis aplicaciones con este método pasan el proceso de revisión, así que no se preocupe por usarlo.
Una posible solución alternativa: cree y agregue una pequeña UIWebView a su vista y ejecute su método loadRequest con el esquema de URL que configuró anteriormente. Esta es una solución y no estoy seguro de lo que Apple dirá al respecto. ¡Buena suerte!
Una versión actualizada de la respuesta de Julio Bailon con la sintaxis Swift moderna:
let url = NSURL(string: "scheme://")!
var responder: UIResponder? = self
while let r = responder {
if r.respondsToSelector("openURL:") {
r.performSelector("openURL:", withObject: url)
break
}
responder = r.nextResponder()
}
No hay necesidad de una extensión para NSObject ahora.
Nota: debe esperar a que la vista se adjunte a la jerarquía de vista antes de llamar a este código; de lo contrario, la cadena de respuesta no se puede usar.
Solución de trabajo (probada en iOS 9.2) para Keyboard Extension. Esta categoría agrega un método especial para acceder al objeto oculto de sharedApplication
y luego llama a openURL:
en él. (Por supuesto, entonces debes usar el método openURL:
con tu esquema de aplicación).
extension UIInputViewController {
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)
}
}