Mostek JavaScript w iOS

102

Pracuję nad aplikacją, w której mam zamiar używać zarówno HTML5 w UIWebView, jak i natywnej struktury iOS. Wiem, że potrafię zaimplementować komunikację między JavaScript a Objective-C. Czy są jakieś biblioteki, które ułatwiają wdrażanie tej komunikacji? Wiem, że istnieje kilka bibliotek do tworzenia natywnych aplikacji na iOS w HTML5 i javascript (na przykład AppMobi, PhoneGap), ale nie jestem pewien, czy istnieje biblioteka pomagająca w tworzeniu natywnych aplikacji na iOS z dużym wykorzystaniem JavaScript. Potrzebuję:

  1. Wykonaj metody JS z Objective-C
  2. Wykonaj metody Objective-C z JS
  3. Słuchaj natywnych zdarzeń JS z Objective-C (na przykład zdarzenia DOM ready)
andr111
źródło
1
Możesz użyć WKWebView: call z javascript window.webkit.messageHandlers. {NAZWA} .postMessage (wiadomość), a następnie obsłużyć go za pomocą [WKUserContentController addScriptMessageHandler: name:], aby wywołać Objective-C z JS
kostyl

Odpowiedzi:

150

Jest kilka bibliotek, ale nie korzystałem z żadnej z nich w dużych projektach, więc możesz je wypróbować:

-

Myślę jednak, że jest to coś na tyle prostego, że możesz spróbować samemu. Osobiście zrobiłem to dokładnie, kiedy musiałem to zrobić. Możesz także utworzyć prostą bibliotekę, która odpowiada Twoim potrzebom.

1. Wykonaj metody JS z Objective-C

To naprawdę tylko jedna linia kodu.

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

Więcej szczegółów w oficjalnej dokumentacji UIWebView .

2. Wykonaj metody Objective-C z JS

Jest to niestety nieco bardziej złożone, ponieważ nie ma tej samej właściwości (i klasy) windowScriptObject, która istnieje w systemie Mac OSX, umożliwiając pełną komunikację między nimi.

Możesz jednak łatwo dzwonić z niestandardowych adresów URL javascript, takich jak:

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

I przechwyć to z Objective-C tym:

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

Nie jest to tak czyste, jak powinno (lub przy użyciu windowScriptObject), ale działa.

3. Słuchaj natywnych zdarzeń JS z Objective-C (na przykład zdarzenia DOM ready)

Z powyższego wyjaśnienia widać, że jeśli chcesz to zrobić, musisz utworzyć kod JavaScript, dołączyć go do zdarzenia, które chcesz monitorować, i wywołać prawidłowe window.locationwywołanie, które zostanie następnie przechwycone.

Ponownie, nie jest czysty, jak powinien, ale działa.

Folletto
źródło
12
Wskazówka: nie umieszczaj w swoim schemacie podkreśleń ani podobnych elementów.
fabb
4
i zwróć wartość, kiedy powinieneś :)
Pizzaiola Gorgonzola
2
Kolejna wskazówka: załaduj adres URL w
ramce
@Folleto Cześć, powiedziałeś „ale nie użyłem żadnego z nich w dużych projektach”, czemu nie? czy znalazłeś kilka problemów w tych bibliotekach ??? Z góry dziękuję.
JERC
1
Nie ma problemów. Po prostu ich nie używałem, więc nie mogę komentować używania ich w większych projektach. Chciałem to wyjaśnić. :)
Folletto
57

Sugerowany sposób wywoływania celu c z JS w zaakceptowanej odpowiedzi nie jest zalecany. Przykład problemu: jeśli wykonasz dwa natychmiastowe połączenia, jedno jest ignorowane (nie możesz zmienić lokalizacji zbyt szybko).

Polecam następujące alternatywne podejście:

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

Wywołujesz executefunkcję wielokrotnie, a ponieważ każde wywołanie jest wykonywane we własnym elemencie iframe, nie należy ich ignorować, gdy są wywoływane szybko.

Podziękowania dla tego gościa .

talkol
źródło
1
Możesz także użyć kolejki w JavaScript, którą iOS może pobrać, aby uniknąć zbyt szybkiej utraty wiadomości z .src. Implementacja połączona powyżej, github.com/marcuswestin/WebViewJavascriptBridge , wykorzystuje podejście kolejkowe.
kevintcoughlin
12

Aktualizacja: Zmieniło się to w iOS 8. Moja odpowiedź dotyczy poprzednich wersji.

Alternatywą, która może spowodować odrzucenie ze sklepu z aplikacjami, jest użycie WebScriptObject.

Te interfejsy API są publiczne w systemie OSX, ale nie w systemie iOS.

Musisz zdefiniować interfejsy do klas wewnętrznych.

@interface WebScriptObject: NSObject
@end

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

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

Musisz zdefiniować obiekt, który będzie służył jako obiekt 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

Teraz ustaw to wystąpienie na swój 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"];
        }
    }
}

Teraz w swoim javascript możesz zadzwonić:

yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
Joseph Lennox
źródło
Moja aplikacja została odrzucona w sklepie z aplikacjami. Problem, który otrzymałem, brzmiał: „Aplikacja zawiera lub dziedziczy niepubliczne klasy w AppName: UIWebDocumentView”
chandru
5

W iOS8 możesz spojrzeć na WKWebView zamiast UIWebView . Ma następującą klasę: WKScriptMessageHandler: udostępnia metodę odbierania komunikatów z JavaScript uruchomionego na stronie internetowej.

Alex Stone
źródło
Oczywiście, ale metoda nie jest wykonywana w sposób ciągły .. to błąd we frameworku, który jest dostarczany przez firmę Apple
Josh Kethepalli,
WKWebViewma wiele problemów, w tym nieprawidłową obsługę plików cookie. Tylko ostrzeżenie dla każdego, kto tu przychodzi, myśląc, że to rozwiąże wszystkie twoje problemy - poszukaj go trochę przed przyjęciem.
CupawnTae
3

Jest to możliwe z iOS7, sprawdź http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/

CocoaChris
źródło
3
Niezupełnie: JavaScriptCore nie współdziała z UIWebViews. To świetne narzędzie, ale do innej klasy problemów. ;)
Folletto
1
Tak: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Jednak JavaScriptCore jest w tej chwili zbyt obciążony, aby mógł być w rzeczywistości użyteczny.
malhal
0

Najlepszym rozwiązaniem jest oferta Appcelerators Titanium. Zbudowali już most javascript Obj-C przy użyciu silnika V8 silnika JavascriptCore używanego przez webkit. Jest to również oprogramowanie typu open source, więc będziesz mógł go pobrać i majstrować przy Obj-C, jak chcesz.

hova
źródło
@hova, nie ma mostka Obj-C V8. V8 jest używany tylko dla Androida.
Ross
0

Przyjrzyj się projektowi KirinJS: Kirin JS, który pozwala na użycie JavaScript do logiki aplikacji i natywnego interfejsu użytkownika odpowiedniego dla platformy, na której działa.

rochal
źródło
czy ktoś nadal przyczynia się do kirin? nie widzę żadnej aktywności w ciągu ostatnich kilku miesięcy ...
Sam,
0

Stworzyłem bibliotekę taką jak WebViewJavascriptBridge, ale jest bardziej podobna do JQuery, jest łatwiejsza w konfiguracji i jest łatwiejsza w użyciu. Nie polega na jQuery (choć trzeba przyznać, że gdybym wiedział, że WebViewJavascriptBridge istniał przed napisaniem tego, mógłbym się nieco powstrzymać przed zanurzeniem się). Powiedz mi co myślisz! jockeyjs

Tim Coulter
źródło
0

Jeśli używasz WKWebView na iOS 8, spójrz na XWebView, który może automatycznie udostępniać natywny interfejs do javascript.

blask
źródło