Cầu nối JavaScript iOS


101

Tôi đang làm việc trên một ứng dụng mà tôi sẽ sử dụng cả HTML5 trong UIWebView và khung iOS gốc cùng nhau. Tôi biết rằng tôi có thể triển khai giao tiếp giữa JavaScript và Objective-C. Có thư viện nào đơn giản hóa việc triển khai giao tiếp này không? Tôi biết rằng có một số thư viện để tạo ứng dụng iOS gốc bằng HTML5 và javascript (ví dụ: AppMobi, PhoneGap), nhưng tôi không chắc liệu có thư viện nào giúp tạo ứng dụng iOS gốc sử dụng nhiều JavaScript hay không. Tôi cần phải:

  1. Thực thi các phương thức JS từ Objective-C
  2. Thực thi các phương thức Objective-C từ JS
  3. Nghe các sự kiện JS gốc từ Objective-C (ví dụ: sự kiện DOM sẵn sàng)

1
Bạn có thể sử dụng WKWebView: cuộc gọi từ window.webkit.messageHandlers javascript {NAME} .postMessage (message) và sau đó xử lý nó với [WKUserContentController addScriptMessageHandler: Tên:] để gọi Objective-C từ JS.
kostyl

Câu trả lời:


150

Có một vài thư viện, nhưng tôi đã không sử dụng bất kỳ thư viện nào trong số này trong các dự án lớn, vì vậy bạn có thể muốn thử chúng:

-

Tuy nhiên, tôi nghĩ đó là một cái gì đó đủ đơn giản để bạn có thể tự mình thử. Cá nhân tôi đã làm chính xác điều này khi tôi cần làm điều đó. Bạn cũng có thể tạo một thư viện đơn giản phù hợp với nhu cầu của mình.

1. Thực thi các phương thức JS từ Objective-C

Đây thực sự chỉ là một dòng mã.

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

Thông tin chi tiết về Tài liệu UIWebView chính thức .

2. Thực thi các phương thức Objective-C từ JS

Điều này không may là phức tạp hơn một chút, bởi vì không có cùng thuộc tính windowScriptObject (và lớp) tồn tại trên Mac OSX cho phép giao tiếp hoàn chỉnh giữa cả hai.

Tuy nhiên, bạn có thể dễ dàng gọi từ các URL tùy chỉnh của javascript, như:

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

Và chặn nó khỏi Objective-C bằng cách:

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

Điều này không phải là sạch như nó phải được (hoặc bằng cách sử dụng windowScriptObject) nhưng nó hoạt động.

3. Nghe các sự kiện JS gốc từ Objective-C (ví dụ: sự kiện DOM sẵn sàng)

Từ phần giải thích trên, bạn thấy rằng muốn làm được điều đó thì bạn phải tạo một đoạn mã JavaScript nào đó, gắn nó vào sự kiện mà bạn muốn theo dõi và gọi chính xác window.locationcuộc gọi mới được chặn.

Một lần nữa, không phải là sạch như nó phải được, nhưng nó hoạt động.


12
Gợi ý: không đặt gạch dưới hoặc tương tự trong lược đồ của bạn.
fabb

4
và trả về một giá trị khi bạn có nghĩa vụ phải :)
Pizzaiola Gorgonzola

2
Gợi ý khác: Load URL trong một iFrame để ngăn chặn nhấp nháy
iCaramba

@Folleto Xin chào, bạn đã nói "nhưng tôi đã không sử dụng bất kỳ cái nào trong số này trong các dự án lớn", tại sao không ?? bạn có tìm thấy một số vấn đề trên các thư viện này không ??? Cảm ơn trước.
JERC

1
Không vấn đề. Tôi chỉ đơn giản là không sử dụng chúng, vì vậy tôi không thể bình luận về việc sử dụng chúng trong các dự án lớn hơn. Tôi muốn làm rõ điều đó. :)
Folletto

57

Phương pháp đề xuất để gọi mục tiêu c từ JS trong câu trả lời được chấp nhận không được khuyến nghị. Một ví dụ về vấn đề: nếu bạn thực hiện hai cuộc gọi liên tiếp ngay lập tức, một cuộc gọi bị bỏ qua (bạn không thể thay đổi vị trí quá nhanh).

Tôi đề xuất phương pháp thay thế sau:

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

Bạn gọi executehàm lặp đi lặp lại và vì mỗi lệnh gọi thực thi trong iframe của chính nó, chúng sẽ không bị bỏ qua khi được gọi nhanh.

Tín dụng cho anh chàng này .


1
Bạn cũng có thể sử dụng hàng đợi trong JavaScript mà iOS có thể tìm nạp để tránh mất thông báo từ .src thay đổi quá nhanh. Việc triển khai được liên kết ở trên, github.com/marcuswestin/WebViewJavascriptBridge , sử dụng phương pháp tiếp cận hàng đợi.
kevintcoughlin

12

Cập nhật: Điều này đã thay đổi trong iOS 8. Câu trả lời của tôi áp dụng cho các phiên bản trước.

Một giải pháp thay thế, có thể khiến bạn bị từ chối khỏi cửa hàng ứng dụng, là sử dụng WebScriptObject.

Các API này công khai trên OSX nhưng không công khai trên iOS.

Bạn cần xác định giao diện cho các lớp bên trong.

@interface WebScriptObject: NSObject
@end

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

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

Bạn cần xác định đối tượng của mình sẽ phục vụ như là WebScriptObject của bạn

@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

Bây giờ đặt một phiên bản cho UIWebView của bạn

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

Bây giờ bên trong javascript của bạn, bạn có thể gọi:

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

Ứng dụng của tôi bị từ chối trong cửa hàng ứng dụng. Sự cố tôi nhận được là "Ứng dụng chứa hoặc kế thừa từ các lớp không công khai trong AppName: UIWebDocumentView"
chandru

5

Trong iOS8, bạn có thể xem WKWebView thay vì UIWebView . Nó có lớp sau: WKScriptMessageHandler: Cung cấp một phương thức để nhận thông báo từ JavaScript đang chạy trong một trang web.


Ofcourse, nhưng phương pháp này là không thực hiện continuoulsy .. đó là một lỗi trong khuôn khổ được cung cấp bởi apple
Josh Kethepalli

WKWebViewcó rất nhiều vấn đề, bao gồm cả việc không xử lý cookie đúng cách. Chỉ là một lời cảnh báo cho bất kỳ ai đến đây nghĩ rằng nó sẽ giải quyết được mọi vấn đề của bạn - hãy google xung quanh một chút trước khi áp dụng nó.
CupawnTae

3

Điều này có thể thực hiện được với iOS7, hãy kiểm tra http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/


3
Không hẳn: JavaScriptCore không tương tác với UIWebViews. Đó là một công cụ tuyệt vời, nhưng dành cho một loại vấn đề khác. ;)
Folletto

1
Có, nó có: JSContext * context = [_webView valueForKeyPath: @ "documentView.webView.mainFrame.javaScriptContext"]; Nhưng JavaScriptCore hiện tại quá nhiều lỗi để có thể sử dụng thực sự.
malhal

0

Đặt cược tốt nhất của bạn là cung cấp Appcelerators Titanium. Họ đã xây dựng một cầu nối javascript obj-C bằng cách sử dụng công cụ V8 JavascriptCore được sử dụng bởi webkit. Nó cũng là mã nguồn mở, vì vậy bạn sẽ có thể tải xuống và sửa đổi Obj-C theo ý muốn.


@hova, không có cầu Obj-C V8. V8 chỉ được sử dụng cho Android.
Ross

0

Hãy xem dự án KirinJS: Kirin JS cho phép sử dụng Javascript cho logic ứng dụng và giao diện người dùng gốc phù hợp với nền tảng mà nó chạy.


có ai còn đóng góp cho kirin không? tôi không thấy bất kỳ hoạt động nào trong vài tháng qua ...
Sam

0

Tôi đã tạo một thư viện như WebViewJavascriptBridge, nhưng nó giống JQuery hơn, dễ cài đặt hơn và dễ sử dụng hơn. Không dựa vào jQuery (mặc dù theo tín dụng của nó, tôi đã biết WebViewJavascriptBridge tồn tại trước khi viết bài này, tôi có thể chỉ hơi kìm lại trước khi đi sâu vào). Cho tôi biết bạn nghĩ gì! jockeyjs


0

Nếu bạn đang sử dụng WKWebView trên iOS 8, hãy xem XWebView có thể tự động hiển thị giao diện gốc cho javascript.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.