iOS JavaScript Bridge

101

Ich arbeite an einer App, in der ich sowohl HTML5 in UIWebView als auch das native iOS-Framework zusammen verwenden werde. Ich weiß, dass ich die Kommunikation zwischen JavaScript und Objective-C implementieren kann. Gibt es Bibliotheken, die die Implementierung dieser Kommunikation vereinfachen? Ich weiß, dass es mehrere Bibliotheken gibt, um native iOS-Apps in HTML5 und Javascript zu erstellen (zum Beispiel AppMobi, PhoneGap), aber ich bin mir nicht sicher, ob es eine Bibliothek gibt, mit der native iOS-Apps mit starker JavaScript-Nutzung erstellt werden können. Ich muss einfach:

  1. Führen Sie JS-Methoden von Objective-C aus
  2. Führen Sie Objective-C-Methoden von JS aus
  3. Hören Sie native JS-Ereignisse von Objective-C ab (z. B. DOM-fähiges Ereignis).
andr111
quelle
1
Sie können WKWebView: Aufruf von Javascript window.webkit.messageHandlers. {NAME} .postMessage (Nachricht) verwenden und dann mit [WKUserContentController addScriptMessageHandler: name:] behandeln, um Objective-C von JS
kostyl

Antworten:

150

Es gibt einige Bibliotheken, aber ich habe keine davon in großen Projekten verwendet. Vielleicht möchten Sie sie ausprobieren:

- -

Ich denke jedoch, dass es so einfach ist, dass Sie es selbst ausprobieren können. Ich persönlich habe genau das getan, als ich das tun musste. Sie können auch eine einfache Bibliothek erstellen, die Ihren Anforderungen entspricht.

1. Führen Sie JS-Methoden von Objective-C aus

Dies ist wirklich nur eine Codezeile.

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

Weitere Details finden Sie in der offiziellen UIWebView-Dokumentation .

2. Führen Sie Objective-C-Methoden von JS aus

Dies ist leider etwas komplexer, da unter Mac OS X nicht dieselbe windowScriptObject-Eigenschaft (und -Klasse) vorhanden ist, die eine vollständige Kommunikation zwischen beiden ermöglicht.

Sie können jedoch problemlos über benutzerdefinierte Javascript-URLs aufrufen, z.

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

Und fangen Sie es von Objective-C damit ab:

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

Dies ist nicht so sauber wie es sein sollte (oder mit windowScriptObject), aber es funktioniert.

3. Hören Sie native JS-Ereignisse von Objective-C ab (z. B. DOM-fähiges Ereignis).

Aus der obigen Erklärung geht hervor, dass Sie, wenn Sie dies tun möchten, JavaScript-Code erstellen, ihn an das zu überwachende Ereignis anhängen und den richtigen window.locationAufruf aufrufen müssen, der dann abgefangen werden soll.

Wieder nicht sauber wie es sein sollte, aber es funktioniert.

Folletto
quelle
12
Hinweis: Fügen Sie Ihrem Schema keine Unterstriche oder ähnliches hinzu.
Fabb
4
und geben Sie einen Wert zurück, wenn Sie sollen :)
Pizzaiola Gorgonzola
2
Ein weiterer Hinweis: Laden Sie die URL in einen iFrame, um ein Flackern zu verhindern
iCaramba
@Folleto Hallo, du hast gesagt "aber ich habe keines davon in großen Projekten verwendet", warum nicht? Haben Sie ein Problem mit diesen Bibliotheken gefunden? Danke im Voraus.
JERC
1
Keine Probleme. Ich habe sie einfach nicht verwendet, daher kann ich mich nicht dazu äußern, sie für größere Projekte zu verwenden. Ich wollte das klarstellen. :)
Folletto
57

Die vorgeschlagene Methode zum Aufrufen von Ziel c von JS in der akzeptierten Antwort wird nicht empfohlen. Ein Beispiel für Probleme: Wenn Sie zwei aufeinanderfolgende Anrufe tätigen, wird einer ignoriert (Sie können den Standort nicht zu schnell ändern).

Ich empfehle den folgenden alternativen Ansatz:

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

Sie rufen die executeFunktion wiederholt auf und da jeder Aufruf in einem eigenen Iframe ausgeführt wird, sollten sie beim schnellen Aufruf nicht ignoriert werden.

Dank an diesen Kerl .

talkol
quelle
1
Sie können auch eine Warteschlange in JavaScript verwenden, die iOS abrufen kann, um zu vermeiden, dass Nachrichten aus .src zu schnell geändert werden. Die oben verlinkte Implementierung, github.com/marcuswestin/WebViewJavascriptBridge , verwendet einen Warteschlangenansatz.
Kevintcoughlin
12

Update: Dies hat sich in iOS 8 geändert. Meine Antwort gilt für frühere Versionen.

Eine Alternative, mit der Sie möglicherweise aus dem App Store abgelehnt werden, ist die Verwendung von WebScriptObject.

Diese APIs sind unter OSX öffentlich, jedoch nicht unter iOS.

Sie müssen Schnittstellen zu den internen Klassen definieren.

@interface WebScriptObject: NSObject
@end

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

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

Sie müssen Ihr Objekt definieren, das als WebScriptObject dienen soll

@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

Stellen Sie nun eine Instanz auf Ihre UIWebView ein

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"];
        }
    }
}

Jetzt können Sie in Ihrem Javascript Folgendes aufrufen:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
Joseph Lennox
quelle
Meine App wird im App Store abgelehnt. Das Problem, das ich erhielt, war "Die App enthält oder erbt von nicht öffentlichen Klassen in AppName: UIWebDocumentView"
chandru
5

In iOS8 können Sie WKWebView anstelle von UIWebView anzeigen . Dies hat die folgende Klasse: WKScriptMessageHandler: Bietet eine Methode zum Empfangen von Nachrichten von JavaScript, die auf einer Webseite ausgeführt werden.

Alex Stone
quelle
Natürlich, aber die Methode führt keine Continuoulsy aus. Es ist ein Fehler in dem Framework, das von Apple bereitgestellt wird
Josh Kethepalli
WKWebViewhat viele Probleme, einschließlich des nicht ordnungsgemäßen Umgangs mit Cookies. Nur eine Warnung für alle, die hierher kommen und glauben, dass sie alle Ihre Probleme lösen werden - googeln Sie ein wenig, bevor Sie sie übernehmen.
CupawnTae
3

Dies ist mit iOS7 möglich, Kasse http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

CocoaChris
quelle
3
Nicht wirklich: JavaScriptCore interagiert nicht mit UIWebViews. Es ist ein großartiges Werkzeug, aber für eine andere Klasse von Problemen. ;)
Folletto
1
Ja, das tut es: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Aber JavaScriptCore ist im Moment zu fehlerhaft, um wirklich von Nutzen zu sein.
Malhal
0

Ihre beste Wahl ist das Angebot von Appcelerators Titanium. Sie haben bereits eine Obj-C-Javascript-Brücke mit der vom Webkit verwendeten V8-Engine JavascriptCore-Engine erstellt. Es ist auch Open Source, so dass Sie es herunterladen und mit dem Obj-C basteln können, wie Sie möchten.

hova
quelle
@hova, das ist keine Obj-C V8 Brücke. V8 wird nur für Android genutzt.
Ross
0

Schauen Sie sich das KirinJS-Projekt an: Kirin JS , mit dem Javascript für die Anwendungslogik und die native Benutzeroberfläche verwendet werden kann, die der Plattform entsprechen, auf der es ausgeführt wird.

Rochal
quelle
Trägt noch jemand zu Kirin bei? Ich sehe keine Aktivität in den letzten Monaten ...
Sam
0

Ich habe eine Bibliothek wie WebViewJavascriptBridge erstellt, aber sie ähnelt eher JQuery, ist einfacher einzurichten und einfacher zu verwenden. Verlässt sich nicht auf jQuery (obwohl ich gewusst hätte, dass WebViewJavascriptBridge existiert, bevor ich dies geschrieben habe, hätte ich mich vor dem Eintauchen möglicherweise etwas zurückgehalten). Lass mich wissen was du denkst! Jockeyjs

Tim Coulter
quelle