Puente JavaScript de iOS

101

Estoy trabajando en una aplicación en la que usaré HTML5 en UIWebView y el marco nativo de iOS juntos. Sé que puedo implementar la comunicación entre JavaScript y Objective-C. ¿Existen bibliotecas que simplifiquen la implementación de esta comunicación? Sé que hay varias bibliotecas para crear aplicaciones nativas de iOS en HTML5 y javascript (por ejemplo, AppMobi, PhoneGap), pero no estoy seguro de si existe una biblioteca para ayudar a crear aplicaciones nativas de iOS con un uso intensivo de JavaScript. Necesito:

  1. Ejecutar métodos JS desde Objective-C
  2. Ejecutar métodos Objective-C desde JS
  3. Escuche eventos JS nativos de Objective-C (por ejemplo, evento listo para DOM)
andr111
fuente
1
Puede usar WKWebView: llamar desde javascript window.webkit.messageHandlers. {NAME} .postMessage (mensaje) y luego manejarlo con [WKUserContentController addScriptMessageHandler: name:] para llamar a Objective-C desde JS
kostyl

Respuestas:

150

Hay algunas bibliotecas, pero no utilicé ninguna de estas en proyectos grandes, por lo que es posible que desee probarlas:

-

Sin embargo, creo que es algo lo suficientemente simple como para que lo pruebes tú mismo. Personalmente hice exactamente esto cuando necesitaba hacer eso. También puede crear una biblioteca simple que se adapte a sus necesidades.

1. Ejecute métodos JS desde Objective-C

En realidad, esta es solo una línea de código.

NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:@"your javascript code string here"];

Más detalles sobre la documentación oficial de UIWebView .

2. Ejecutar métodos Objective-C desde JS

Desafortunadamente, esto es un poco más complejo, porque no existe la misma propiedad (y clase) windowScriptObject que existe en Mac OSX que permita una comunicación completa entre los dos.

Sin embargo, puede llamar fácilmente desde URL personalizadas de JavaScript, como:

window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value

E interceptarlo de Objective-C con esto:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
   NSURL *URL = [request URL]; 
   if ([[URL scheme] isEqualToString:@"yourscheme"]) {
       // parse the rest of the URL object and execute functions
   } 
}

Esto no es tan limpio como debería ser (o usando windowScriptObject) pero funciona.

3. Escuche eventos JS nativos de Objective-C (por ejemplo, evento listo para DOM)

De la explicación anterior, verá que si desea hacer eso, debe crear un código JavaScript, adjuntarlo al evento que desea monitorear y llamar a la window.locationllamada correcta para que luego sea interceptada.

Una vez más, no está limpio como debería estar, pero funciona.

Folletto
fuente
12
Pista: no pongas guiones bajos o similares en tu esquema.
Fabb
4
y devolver un valor cuando se supone que debe :)
Pizzaiola Gorgonzola
2
Otra pista: cargue la URL en un iFrame para evitar el parpadeo
iCaramba
@Folleto Hola, dijiste "pero no usé ninguno de estos en grandes proyectos", ¿por qué no? ¿Encontraste algún problema en estas bibliotecas ??? Gracias por adelantado.
JERC
1
Sin problemas. Simplemente no los usé, por lo que no puedo comentar sobre su uso en proyectos más grandes. Quería aclarar eso. :)
Folletto
57

No se recomienda el método sugerido para llamar al objetivo c desde JS en la respuesta aceptada. Un ejemplo de problemas: si realiza dos llamadas consecutivas inmediatas, una se ignora (no puede cambiar de ubicación demasiado rápido).

Recomiendo el siguiente enfoque alternativo:

function execute(url) 
{
  var iframe = document.createElement("IFRAME");
  iframe.setAttribute("src", url);
  document.documentElement.appendChild(iframe);
  iframe.parentNode.removeChild(iframe);
  iframe = null;
}

Usted llama a la executefunción repetidamente y, dado que cada llamada se ejecuta en su propio iframe, no deben ignorarse cuando se llaman rápidamente.

Créditos a este chico .

talkol
fuente
1
También puede usar una cola en JavaScript que iOS puede recuperar para evitar perder mensajes de .src que cambian demasiado rápido. La implementación vinculada anteriormente, github.com/marcuswestin/WebViewJavascriptBridge , utiliza un enfoque de cola.
kevintcoughlin
12

Actualización: esto ha cambiado en iOS 8. Mi respuesta se aplica a versiones anteriores.

Una alternativa, que puede hacer que lo rechacen de la tienda de aplicaciones, es usar WebScriptObject.

Estas API son públicas en OSX pero no en iOS.

Necesita definir interfaces para las clases internas.

@interface WebScriptObject: NSObject
@end

@interface WebView
- (WebScriptObject *)windowScriptObject;
@end

@interface UIWebDocumentView: UIView
- (WebView *)webView;
@end

Necesita definir su objeto que servirá como su WebScriptObject

@interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
@end

static WebScriptBridge *gWebScriptBridge = nil;

@implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
    NSLog(bar);
}

-(void)testfoo {
    NSLog(@"testfoo!");
}

+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
    return NO;
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
    return NO;
}

+ (NSString *)webScriptNameForSelector:(SEL)sel
{
    // Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
    if (sel == @selector(testfoo)) return @"testfoo";
    if (sel == @selector(someEvent::)) return @"someEvent";

    return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
    if (gWebScriptBridge == nil)
        gWebScriptBridge = [WebScriptBridge new];

    return gWebScriptBridge;
}
@end

Ahora configure esa instancia en su UIWebView

if ([uiWebView.subviews count] > 0) {
    UIView *scrollView = uiWebView.subviews[0];

    for (UIView *childView in scrollView.subviews) {
        if ([childView isKindOfClass:[UIWebDocumentView class]]) {
            UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
            WebScriptObject *wso = documentView.webView.windowScriptObject;

            [wso setValue:[WebScriptBridge getWebScriptBridge] forKey:@"yourBridge"];
        }
    }
}

Ahora, dentro de su javascript, puede llamar:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
Joseph Lennox
fuente
Mi aplicación es rechazada en la tienda de aplicaciones. El problema que recibí fue "La aplicación contiene o hereda de clases no públicas en AppName: UIWebDocumentView"
chandru
5

En iOS8 puede mirar WKWebView en lugar de UIWebView . Tiene la siguiente clase: WKScriptMessageHandler: proporciona un método para recibir mensajes de JavaScript que se ejecutan en una página web.

Alex Stone
fuente
Por supuesto, pero el método no se ejecuta de forma continua ... es un error en el marco que proporciona Apple
Josh Kethepalli
WKWebViewtiene muchos problemas, incluido el no manejar las cookies correctamente. Solo una advertencia para cualquiera que venga aquí pensando que resolverá todos sus problemas: busque en Google un poco antes de adoptarlo.
CupawnTae
3

Esto es posible con iOS7, consulte http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

CocoaChris
fuente
3
Realmente no: JavaScriptCore no interactúa con UIWebViews. Es una gran herramienta, pero para una clase diferente de problemas. ;)
Folletto
1
Sí, lo hace: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Pero JavaScriptCore tiene demasiados errores en este momento para ser de utilidad real.
Malhal
0

Su mejor apuesta es la oferta de Appcelerators Titanium. Ya han construido un puente javascript Obj-C utilizando el motor JavascriptCore del motor V8 utilizado por webkit. También es de código abierto, por lo que podrás descargarlo y jugar con el Obj-C como quieras.

hova
fuente
@hova, no hay puente Obj-C V8. V8 solo se aprovecha para Android.
Ross
0

Eche un vistazo al proyecto KirinJS: Kirin JS que permite usar Javascript para la lógica de la aplicación y la interfaz de usuario nativa adecuada a la plataforma en la que se ejecuta.

rochal
fuente
¿Alguien todavía contribuye a kirin? No veo ninguna actividad en los últimos meses ...
Sam
0

Creé una biblioteca como WebViewJavascriptBridge, pero es más similar a JQuery, es más fácil de configurar y es más fácil de usar. No depende de jQuery (aunque para su mérito, si hubiera sabido que WebViewJavascriptBridge existía antes de escribir esto, podría haberme retenido un poco antes de sumergirme). ¡Déjame saber lo que piensas! jockeyjs

Tim Coulter
fuente
0

Si está utilizando WKWebView en iOS 8, eche un vistazo a XWebView que puede exponer automáticamente la interfaz nativa a javascript.

soflare
fuente