ponte iOS JavaScript

101

Estou trabalhando em um aplicativo em que vou usar HTML5 no UIWebView e a estrutura nativa do iOS juntos. Eu sei que posso implementar a comunicação entre JavaScript e Objective-C. Existem bibliotecas que simplificam a implementação desta comunicação? Eu sei que existem várias bibliotecas para criar aplicativos iOS nativos em HTML5 e javascript (por exemplo AppMobi, PhoneGap), mas não tenho certeza se existe uma biblioteca para ajudar a criar aplicativos iOS nativos com uso intenso de JavaScript. Eu preciso:

  1. Execute métodos JS de Objective-C
  2. Execute métodos Objective-C de JS
  3. Ouça eventos JS nativos de Objective-C (por exemplo, evento pronto para DOM)
andr111
fonte
1
Você pode usar WKWebView: call from javascript window.webkit.messageHandlers. {NAME} .postMessage (message) e, em seguida, lidar com [WKUserContentController addScriptMessageHandler: name:] para chamar Objective-C de JS
kostyl

Respostas:

150

Existem algumas bibliotecas, mas não usei nenhuma em grandes projetos, então você pode querer experimentá-las:

-

No entanto, acho que é algo simples o suficiente para que você possa experimentar. Eu pessoalmente fiz exatamente isso quando precisei fazer isso. Você também pode criar uma biblioteca simples que atenda às suas necessidades.

1. Execute métodos JS de Objective-C

Esta é realmente apenas uma linha de código.

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

Mais detalhes na documentação oficial do UIWebView .

2. Execute métodos Objective-C de JS

Infelizmente, isso é um pouco mais complexo, porque não há a mesma propriedade windowScriptObject (e classe) que existe no Mac OSX, permitindo a comunicação completa entre os dois.

No entanto, você pode chamar facilmente a partir de URLs javascript personalizados, como:

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

E intercepte-o do Objective-C com este:

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

Isso não é tão claro quanto deveria (ou usando windowScriptObject), mas funciona.

3. Ouça eventos JS nativos de Objective-C (por exemplo, evento pronto para DOM)

A partir da explicação acima, você vê que se quiser fazer isso, você deve criar algum código JavaScript, anexá-lo ao evento que deseja monitorar e chamar a window.locationchamada correta para ser interceptada.

Novamente, não está limpo como deveria, mas funciona.

Folletto
fonte
12
Dica: não coloque sublinhados ou similares em seu esquema.
fabb
4
e retorne um valor quando deveria :)
Pizzaiola Gorgonzola
2
Outra dica: carregue o URL em um iFrame para evitar oscilações
iCaramba
@Folleto Olá, você disse "mas não usei nenhum desses em grandes projetos", por que não ?? você encontrou vários problemas nessas bibliotecas ?? Desde já, obrigado.
JERC
1
Sem problemas. Eu simplesmente não os usei, então não posso comentar sobre como usá-los em projetos maiores. Eu queria esclarecer isso. :)
Folletto
57

O método sugerido de chamar o objetivo c de JS na resposta aceita não é recomendado. Um exemplo de problemas: se você fizer duas chamadas consecutivas imediatamente, uma será ignorada (você não pode mudar de local muito rapidamente).

Eu recomendo a seguinte abordagem alternativa:

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

Você chama a executefunção repetidamente e, como cada chamada é executada em seu próprio iframe, elas não devem ser ignoradas quando chamadas rapidamente.

Créditos para esse cara .

talkol
fonte
1
Você também pode usar uma fila em JavaScript que o iOS pode buscar para evitar a perda de mensagens de .src que mudam muito rapidamente. A implementação vinculada acima, github.com/marcuswestin/WebViewJavascriptBridge , usa uma abordagem de fila.
kevintcoughlin
12

Atualização: isso mudou no iOS 8. Minha resposta se aplica às versões anteriores.

Uma alternativa, que pode fazer com que você seja rejeitado na app store, é usar WebScriptObject.

Essas APIs são públicas no OSX, mas não no iOS.

Você precisa definir interfaces para as classes internas.

@interface WebScriptObject: NSObject
@end

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

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

Você precisa definir o seu objeto que servirá como seu 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

Agora defina que uma instância para seu 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"];
        }
    }
}

Agora, dentro do seu javascript, você pode chamar:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
Joseph Lennox
fonte
Meu aplicativo foi rejeitado na app store. O problema que recebi foi "O aplicativo contém ou herda de classes não públicas em AppName: UIWebDocumentView"
chandru
5

No iOS8, você pode olhar para WKWebView em vez de UIWebView . Tem a seguinte classe: WKScriptMessageHandler: fornece um método para receber mensagens de JavaScript em execução em uma página da web.

Alex Stone
fonte
Claro, mas o método não está executando continuamente ... é um bug no framework que é fornecido pela apple
Josh Kethepalli
WKWebViewtem muitos problemas, incluindo o não manuseio adequado dos cookies. Apenas um aviso para quem vier aqui pensando que vai resolver todos os seus problemas - dê uma olhada antes de adotar.
CupawnTae
3

Isso é possível com iOS7, confira http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

CocoaChris
fonte
3
Na verdade, não: JavaScriptCore não interage com UIWebViews. É uma ótima ferramenta, mas para uma classe diferente de problemas. ;)
Folletto
1
Sim, é verdade: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Mas JavaScriptCore é muito problemático no momento para ter alguma utilidade real.
Malhal
0

Sua melhor aposta é a oferta de Appcelerators Titanium. Eles já construíram uma ponte javascript Obj-C usando o motor V8 JavascriptCore usado pelo webkit. Também é um código aberto, então você poderá baixá-lo e mexer no Obj-C como quiser.

Hova
fonte
@hova, não há ponte Obj-C V8. V8 é aproveitado apenas para Android.
Ross,
0

Dê uma olhada no projeto KirinJS: Kirin JS que permite usar Javascript para a lógica da aplicação e UI nativa adequada à plataforma em que roda.

Rochal
fonte
alguém ainda contribui para o kirin? não vi nenhuma atividade nos últimos meses ...
Sam
0

Eu criei uma biblioteca como WebViewJavascriptBridge, mas é mais parecida com JQuery, é mais fácil de configurar e é mais fácil de usar. Não depende do jQuery (embora, para seu crédito, se eu soubesse que o WebViewJavascriptBridge existia antes de escrever isso, poderia ter me contido um pouco antes de mergulhar). Diz-me o que pensas! jockeyjs

Tim Coulter
fonte
0

Se você estiver usando WKWebView no iOS 8, dê uma olhada no XWebView, que pode expor automaticamente a interface nativa para javascript.

soflare
fonte