Bridge JavaScript iOS


101

Sto lavorando a un'app in cui userò sia HTML5 in UIWebView che il framework iOS nativo insieme. So di poter implementare la comunicazione tra JavaScript e Objective-C. Esistono biblioteche che semplificano l'implementazione di questa comunicazione? So che ci sono diverse librerie per creare app iOS native in HTML5 e javascript (ad esempio AppMobi, PhoneGap), ma non sono sicuro che esista una libreria per creare app iOS native con un utilizzo intenso di JavaScript. Ho bisogno di:

  1. Esegui metodi JS da Objective-C
  2. Esegui metodi Objective-C da JS
  3. Ascolta eventi JS nativi da Objective-C (ad esempio evento pronto per DOM)

1
Puoi usare WKWebView: call da javascript window.webkit.messageHandlers. {NAME} .postMessage (message) e quindi gestirlo con [WKUserContentController addScriptMessageHandler: name:] per chiamare Objective-C da JS
kostyl

Risposte:


150

Ci sono alcune librerie, ma non ho usato nessuna di queste in grandi progetti, quindi potresti volerle provare:

-

Tuttavia, penso che sia qualcosa di abbastanza semplice che potresti provarlo tu stesso. Personalmente ho fatto esattamente questo quando avevo bisogno di farlo. Potresti anche creare una semplice libreria adatta alle tue esigenze.

1. Eseguire i metodi JS da Objective-C

Questa è davvero solo una riga di codice.

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

Maggiori dettagli sulla documentazione ufficiale di UIWebView .

2. Eseguire i metodi Objective-C da JS

Questo è purtroppo leggermente più complesso, perché non esiste la stessa proprietà (e classe) windowScriptObject che esiste su Mac OSX che consente la comunicazione completa tra i due.

Tuttavia, puoi facilmente chiamare da URL personalizzati javascript, come:

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

E intercettalo da Objective-C con questo:

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

Non è così pulito come dovrebbe essere (o usando windowScriptObject) ma funziona.

3. Ascolta eventi JS nativi da Objective-C (ad esempio evento pronto per DOM)

Dalla spiegazione sopra, vedi che se vuoi farlo, devi creare del codice JavaScript, allegarlo all'evento che vuoi monitorare e chiamare la window.locationchiamata corretta per essere poi intercettato.

Di nuovo, non è pulito come dovrebbe essere, ma funziona.


12
Suggerimento: non inserire trattini bassi o simili nel tuo schema.
fabb

4
e restituisci un valore quando dovresti :)
Pizzaiola Gorgonzola

2
Un altro suggerimento: carica l'URL in un iFrame per evitare lo sfarfallio
iCaramba

@Folleto Ciao, hai detto "ma non ho usato nessuno di questi in grandi progetti", perché no ?? hai trovato diversi problemi su queste librerie ??? Grazie in anticipo.
JERC

1
Senza problemi. Semplicemente non li ho usati, quindi non posso commentare il loro utilizzo su progetti più grandi. Volevo chiarirlo. :)
Folletto

57

Il metodo suggerito per chiamare l'obiettivo c da JS nella risposta accettata non è raccomandato. Un esempio di problemi: se effettui due chiamate consecutive immediate, una viene ignorata (non puoi cambiare posizione troppo velocemente).

Raccomando il seguente approccio alternativo:

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

Chiami la executefunzione ripetutamente e poiché ogni chiamata viene eseguita nel proprio iframe, non dovrebbero essere ignorate se chiamate rapidamente.

Crediti a questo ragazzo .


1
Puoi anche utilizzare una coda in JavaScript che iOS può recuperare per evitare di perdere messaggi da .src che cambiano troppo rapidamente. L'implementazione collegata sopra, github.com/marcuswestin/WebViewJavascriptBridge , utilizza un approccio a coda.
kevintcoughlin

12

Aggiornamento: questo è cambiato in iOS 8. La mia risposta si applica alle versioni precedenti.

Un'alternativa, che potrebbe farti rifiutare dall'App Store, è usare WebScriptObject.

Queste API sono pubbliche su OSX ma non su iOS.

È necessario definire le interfacce per le classi interne.

@interface WebScriptObject: NSObject
@end

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

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

Devi definire il tuo oggetto che servirà come 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

Ora imposta un'istanza 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"];
        }
    }
}

Ora all'interno del tuo javascript puoi chiamare:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();

La mia app è stata rifiutata nell'app store. Il problema che ho ricevuto era "L'app contiene o eredita da classi non pubbliche in AppName: UIWebDocumentView"
chandru

5

In iOS8 puoi guardare WKWebView invece di UIWebView . Ha la seguente classe: WKScriptMessageHandler: fornisce un metodo per ricevere messaggi da JavaScript in esecuzione in una pagina web.


Ovviamente, ma il metodo non viene eseguito in modo continuo .. è un bug nel framework fornito da Apple
Josh Kethepalli,

WKWebViewha molti problemi, incluso il non gestire correttamente i cookie. Solo un avvertimento per chiunque venga qui pensando che risolverà tutti i tuoi problemi: cerca un po 'prima di adottarlo.
CupawnTae

3

Questo è possibile con iOS7, controlla http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/


3
Non proprio: JavaScriptCore non interagisce con UIWebViews. È un ottimo strumento, ma per una diversa classe di problemi. ;)
Folletto

1
Sì, lo fa: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Ma JavaScriptCore è troppo bacato al momento per essere di reale utilità.
malhal

0

La soluzione migliore è l'offerta di Appcelerators Titanium. Hanno già costruito un ponte javascript Obj-C utilizzando il motore V8 JavascriptCore utilizzato da webkit. È anche open source, quindi potrai scaricarlo e armeggiare con Obj-C come preferisci.


@hova, non c'è il ponte Obj-C V8. V8 è utilizzato solo per Android.
Ross

0

Dai uno sguardo al progetto KirinJS: Kirin JS che permette di utilizzare Javascript per la logica dell'applicazione e UI nativa adeguata alla piattaforma su cui gira.


qualcuno contribuisce ancora a kirin? non vedo alcuna attività negli ultimi mesi ...
Sam

0

Ho creato una libreria come WebViewJavascriptBridge, ma è più simile a JQuery, è più facile da configurare ed è più facile da usare. Non si basa su jQuery (anche se a suo merito, se avessi saputo che WebViewJavascriptBridge esisteva prima di scrivere questo, potrei essermi trattenuto leggermente prima di immergermi). Fatemi sapere cosa ne pensate! jockeyjs


0

Se stai usando WKWebView su iOS 8, dai un'occhiata a XWebView che può esporre automaticamente l'interfaccia nativa a javascript.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.