Pont JavaScript iOS

101

Je travaille sur une application où je vais utiliser à la fois HTML5 dans UIWebView et le framework iOS natif ensemble. Je sais que je peux implémenter la communication entre JavaScript et Objective-C. Existe-t-il des bibliothèques qui simplifient la mise en œuvre de cette communication? Je sais qu'il existe plusieurs bibliothèques pour créer des applications iOS natives en HTML5 et javascript (par exemple AppMobi, PhoneGap), mais je ne suis pas sûr qu'il existe une bibliothèque pour aider à créer des applications iOS natives avec une utilisation intensive de JavaScript. J'ai besoin de:

  1. Exécuter des méthodes JS à partir d'Objective-C
  2. Exécuter des méthodes Objective-C à partir de JS
  3. Écoutez les événements JS natifs d'Objective-C (par exemple, l'événement DOM ready)
andr111
la source
1
Vous pouvez utiliser WKWebView: appel depuis javascript window.webkit.messageHandlers. {NAME} .postMessage (message) puis le gérer avec [WKUserContentController addScriptMessageHandler: name:] pour appeler Objective-C depuis JS
kostyl

Réponses:

150

Il existe quelques bibliothèques, mais je n'en ai utilisé aucune dans de grands projets, vous voudrez peut-être les essayer:

-

Cependant, je pense que c'est quelque chose d'assez simple pour que vous puissiez l'essayer vous-même. J'ai personnellement fait exactement cela quand j'avais besoin de le faire. Vous pouvez également créer une bibliothèque simple qui répond à vos besoins.

1. Exécutez les méthodes JS à partir d'Objective-C

Ce n'est en réalité qu'une seule ligne de code.

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

Plus de détails sur la documentation officielle d' UIWebView .

2. Exécutez les méthodes Objective-C à partir de JS

Ceci est malheureusement un peu plus complexe, car il n'y a pas la même propriété windowScriptObject (et la même classe) qui existe sur Mac OSX permettant une communication complète entre les deux.

Cependant, vous pouvez facilement appeler à partir d'URL personnalisées javascript, telles que:

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

Et interceptez-le d'Objective-C avec ceci:

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

Ce n'est pas aussi propre qu'il devrait l'être (ou en utilisant windowScriptObject) mais cela fonctionne.

3. Écoutez les événements JS natifs d'Objective-C (par exemple, l'événement DOM ready)

D'après l'explication ci-dessus, vous voyez que si vous voulez faire cela, vous devez créer du code JavaScript, le joindre à l'événement que vous souhaitez surveiller et appeler le bon window.locationappel pour être ensuite intercepté.

Encore une fois, pas propre comme il se doit, mais cela fonctionne.

Folletto
la source
12
Astuce: ne mettez pas de traits de soulignement ou similaires dans votre schéma.
fabb du
4
et renvoyez une valeur lorsque vous êtes censé :)
Pizzaiola Gorgonzola
2
Un autre conseil: chargez l'URL dans un iFrame pour éviter le scintillement
iCaramba
@Folleto Salut, vous avez dit "mais je n'ai utilisé aucun de ceux-ci dans de grands projets", pourquoi pas ?? avez-vous trouvé plusieurs problèmes sur ces bibliothèques ??? Merci d'avance.
JERC
1
Pas d'issues. Je ne les ai tout simplement pas utilisés, donc je ne peux pas commenter leur utilisation sur des projets plus importants. Je voulais clarifier cela. :)
Folletto
57

La méthode suggérée pour appeler l'objectif c depuis JS dans la réponse acceptée n'est pas recommandée. Un exemple de problèmes: si vous effectuez deux appels consécutifs immédiats, l'un est ignoré (vous ne pouvez pas changer d'emplacement trop rapidement).

Je recommande l'approche alternative suivante:

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

Vous appelez la executefonction à plusieurs reprises et comme chaque appel s'exécute dans sa propre iframe, ils ne doivent pas être ignorés lorsqu'ils sont appelés rapidement.

Crédits à ce gars .

talkol
la source
1
Vous pouvez également utiliser une file d'attente en JavaScript que iOS peut récupérer pour éviter de perdre des messages de .src changeant trop rapidement. L'implémentation liée ci-dessus, github.com/marcuswestin/WebViewJavascriptBridge , utilise une approche de file d'attente.
kevintcoughlin
12

Mise à jour: cela a changé dans iOS 8. Ma réponse s'applique aux versions précédentes.

Une alternative, qui peut vous faire rejeter de l'App Store, consiste à utiliser WebScriptObject.

Ces API sont publiques sur OSX mais pas sur iOS.

Vous devez définir des interfaces avec les classes internes.

@interface WebScriptObject: NSObject
@end

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

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

Vous devez définir votre objet qui servira de 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

Maintenant, définissez cette instance sur votre 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"];
        }
    }
}

Maintenant, dans votre javascript, vous pouvez appeler:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
Joseph Lennox
la source
Mon application est rejetée dans l'App Store. Le problème que j'ai reçu était "L'application contient ou hérite de classes non publiques dans AppName: UIWebDocumentView"
chandru
5

Dans iOS8, vous pouvez consulter WKWebView au lieu de UIWebView . Cela a la classe suivante: WKScriptMessageHandler: Fournit une méthode pour recevoir des messages de JavaScript s'exécutant dans une page Web.

Alex Stone
la source
Bien sûr, mais la méthode ne s'exécute pas continuellement .. c'est un bogue dans le framework fourni par apple
Josh Kethepalli
WKWebViewa beaucoup de problèmes, y compris une mauvaise gestion des cookies. Juste un avertissement pour quiconque vient ici pensant que cela résoudra tous vos problèmes - recherchez un peu sur Google avant de l'adopter.
CupawnTae
3

Ceci est possible avec iOS7, consultez http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

CocoaChris
la source
3
Pas vraiment: JavaScriptCore n'interagit pas avec UIWebViews. C'est un excellent outil, mais pour une classe de problèmes différente. ;)
Folletto
1
Oui, c'est le cas: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Mais JavaScriptCore est trop bogué pour le moment pour être réellement utile.
malhal le
0

Votre meilleur pari est l'offre Appcelerators Titanium. Ils ont déjà construit un pont javascript Obj-C en utilisant le moteur JavascriptCore du moteur V8 utilisé par webkit. Il est également open source, vous pourrez donc le télécharger et bricoler l'Obj-C comme vous le souhaitez.

hova
la source
@hova, il n'y a pas de pont Obj-C V8. Le V8 n'est exploité que pour Android.
Ross
0

Jetez un œil au projet KirinJS: Kirin JS qui permet d'utiliser Javascript pour la logique applicative et l'interface utilisateur native adaptée à la plateforme sur laquelle il s'exécute.

rochal
la source
est-ce que quelqu'un contribue encore à kirin? Je ne vois aucune activité ces derniers mois ...
Sam
0

J'ai créé une bibliothèque comme WebViewJavascriptBridge, mais elle ressemble plus à JQuery, est plus facile à configurer et à utiliser. Ne repose pas sur jQuery (bien qu'à son crédit, si j'avais su que WebViewJavascriptBridge existait avant d'écrire ceci, je me serais peut-être un peu retenu avant de plonger). Laissez-moi savoir ce que vous pensez! jockeyjs

Tim Coulter
la source
0

Si vous utilisez WKWebView sur iOS 8, jetez un œil au XWebView qui peut automatiquement exposer l'interface native à javascript.

soflare
la source