最近在開發一個 iOS app,需要使用混合模式(native + WebView),當中有些部份需要 Javascript 和 Objective-C 雙向溝通,要在 Objective-C中使用 Javascript function 並不難,使用 UIWebView 中的 “stringByEvaluatingJavaScriptFromString” 就可以了,要在 Javascript 中使用 Objective-C 比較難一點點,需要 implement UIWebViewDelegate的 “shouldStartLoadWithRequest”。
Android 在這一方面做得很簡單,在 WebView 裏有一個 method 叫做 “addJavascriptInterface”,可以讓開發者自由加入 Javascript 的 Interface,在 Javascript 中就可以直接使用 Java 的 function,在 Objective-C 中我們可以複制一下這個模式嗎?
當然可以,不然我為什麼要寫這篇文章?
市面上已經有一個挺方便的 class 可以用,就是這個 Native Bridge,然而用了之後你會發現有些問題;第一它要 return 數值到 Javascript 的話要用 async-style,這樣你的程式碼看起來會過份臃腫。第二讓 Javascript 使用的 function 要全部寫到 UIWebView 的 handleCall 中,用起上來不夠簡潔。讓我們來解決這個問題吧!
首先我們要繼承一下 UIWebView,並加入以下 method。
1
2
3
|
-(
void
) addJavascriptInterfaces:(
NSObject
*)interfaceWithName:(
NSString
*) name{
[
self
.proxyDelegate addJavascriptInterfaces:interfaceWithName:name];
}
|
method signature 就跟 Android 裏的一樣,裏面會使用 proxy delegate 的 addJavascriptInterfaces,為什麼要用 proxy delegate 呢?因為要避免佔據了 UIWebView 的 delegate property 呀,不然其他用家怎樣使用它來 intercept 其他 request 呢!
Proxy Delegate 就是這個 library 的主角,它主要 implement 了兩個 method 來加入我們想要的功能,一是 webViewDidStartLoad,一是 shouldStartLoadWithRequest。
在 webViewDidStartLoad 時我們要把我們的 Javascript 注入到 UIWebView 中,這包括兩個部份。第一部份是我們的 Javascript 基本代碼,這包括了兩個 function,call() 用來 call Objective-C Interface 的 method,inject() 用來注入 Interface 內所有 method signature 到 Javascript 去。第二部份是我們 runtime 生成的 method signature,會使用 inject() 注入到 Javascript 中。
在 shouldStartLoadWithRequest 時我們要 intercept 一下,看看這個 request 是不是 easy-js: protocol,是的話我們就用 reflection 的方法去呼叫我們的 Objective-C Interface,並且把 return value 用 Javascript 再注入到 UIWebView 中,這樣我們便不用等到 request 完才用 async 的方法把 return value 傳到 UIWebView 去。
用文字解釋這個 library 實在不容易,大家還是看代碼更簡單。
GitHub: EasyJSWebView
Sample project: EasyJSWebViewSample
使用方法:
1. 建立一個 Javascript Interface Class
1
2
3
4
5
6
7
8
9
|
@interface
MyJSInterface:
NSObject
-(
void
) test;
-(
void
) testWithParam:(
NSString
*) param;
-(
void
) testWithTwoParam:(
NSString
*) param AndParam2:(
NSString
*) param2;
-(
NSString
*) testWithRet;
@end
|
2. 把它加入到 WebView 裏
1
2
3
|
MyJSInterface*interface=[MyJSInterface
new
];
[
self
.myWebView addJavascriptInterfaces:interfaceWithName:@
"MyJSTest"
];
[interface release];
|
3. 在 Javascript 中可以直接使用了
1
2
3
4
5
|
MyJSTest.test();
MyJSTest.testWithParam(
"ha:ha"
);
MyJSTest.testWithTwoParamAndParam2(
"haha1"
,
"haha2"
);
var
str =MyJSTest.testWithRet();
|
更多其他例子請到 github 的 sample project 裏閱讀吧。