隨着蘋果SDK的不斷升級,愈來愈多的新特性增長了進來,本文主要講述從iOS6至今,Native與JavaScript的交互方法javascript
iOS6原生沒有提供js直接調用Objective-C的方式,只能經過UIWebView的UIWebViewDelegate協議html
1
|
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
|
方法來作攔截,並在這個方法中,根據url來調用Objective-C方法java
動態添加個iframe改變其地址 最後刪除,這種方法不會使當前頁面跳轉 效果更佳git
javascript代碼:github
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function callOC2(func,param){
var iframe = document.createElement("iframe");
var url= "myapp:" + "&func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.parentNode.removeChild(iFrame);
iframe = null;
}
|
1
2
|
使用方法
<input type="button" value="傳個字典2" onclick="callOC2('testFunc',{'param1':76,'param2':155,'param3':76})" />
|
Objective-C代碼:web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ];
if ([requestString hasPrefix:@"myapp:"]) {
NSLog(@"requestString:%@",requestString);
//若是是本身定義的協議, 再截取協議中的方法和參數, 判斷無誤後在這裏手動調用oc方法
NSMutableDictionary *param = [self queryStringToDictionary:requestString];
NSLog(@"get param:%@",[param description]);
NSString *func = [param objectForKey:@"func"];
if([func isEqualToString:@"callFunc"])
{
[self testFunc:[param objectForKey:@"first"] withParam2:[param objectForKey:@"second"] andParam3:[param objectForKey:@"third"] ];
}
/*
* 方法的返回值是BOOL值。
* 返回YES:表示讓瀏覽器執行默認操做,好比某個a連接跳轉
* 返回NO:表示不執行瀏覽器的默認操做,這裏由於經過url協議來判斷js執行native的操做,確定不是瀏覽器默認操做,故返回NO
*
*/
return NO;
}
return YES;
}
|
1
2
3
4
5
6
7
8
9
|
- (NSMutableDictionary*)queryStringToDictionary:(NSString*)string {
NSMutableArray *elements = (NSMutableArray*)[string componentsSeparatedByString:@"&"];
NSMutableDictionary *retval = [NSMutableDictionary dictionaryWithCapacity:[elements count]];
for(NSString *e in elements) {
NSArray *pair = [e componentsSeparatedByString:@"="];
[retval setObject:[pair objectAtIndex:1]?:@"" forKey:[pair objectAtIndex:0]?@:"nokey"];
}
return retval;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//插入js 而且執行傳值
- (IBAction)insertJSTouched:(id)sender {
NSString *insertString = [NSString stringWithFormat:
@"var script = document.createElement('script');"
"script.type = 'text/javascript';"
"script.text = \"function jsFunc() { "
"var a=document.getElementsByTagName('body')[0];"
"alert('%@');"
"}\";"
"document.getElementsByTagName('head')[0].appendChild(script);", self.someString];
NSLog(@"insert string %@",insertString);
[self.myWeb stringByEvaluatingJavaScriptFromString:insertString];
[self.myWeb stringByEvaluatingJavaScriptFromString:@"jsFunc();"];
}
|
1
2
3
4
|
//提交form表單
- (IBAction)submitTouched:(id)sender {
[self.myWeb stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit(); "];
}
|
1
2
3
4
5
|
//修改標籤屬性
- (IBAction)fontTouched:(id)sender {
NSString *tempString2 = [NSString stringWithFormat:@"document.getElementsByTagName('p')[0].style.fontSize='%@';",@"19px"];
[self.myWeb stringByEvaluatingJavaScriptFromString:tempString2];
}
|
(PS)若是你想去掉webview彈出的alert 中的來自XXX網頁sublime-text
1
2
3
4
5
|
- (void)webViewDidFinishLoad: (UIWebView *) webView
{
//重定義web的alert方法,捕獲webview彈出的原生alert 能夠修改標題和內容等等
[webView stringByEvaluatingJavaScriptFromString:@"window.alert = function(message) { window.location = \"myapp:&func=alert&message=\" + message; }"];
}
|
1
2
3
4
|
if([func isEqualToString:@"alert"])
{
[self showMessage:@"來自網頁的提示" message:[param objectForKey:@"message"]];
}
|
iOS7中加入了JavaScriptCore.framework框架。把 WebKit 的 JavaScript 引擎用 Objective-C 封裝。該框架讓Objective-C和JavaScript代碼直接的交互變得更加的簡單方便。api
分兩種方式數組
第一在渲染網頁時遇到<script標籤時,就會建立JSContext環境去運行JavaScript代碼。瀏覽器
第二就是使用方法[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]去獲取JSContext環境時,這時不管是否遇到<script標籤,都會去創造出來一個JSContext環境,並且和遇到<script標籤再創造環境是同一個。
我一般都會在 - (void)webViewDidFinishLoad:(UIWebView *)webView中去注入交互對象,可是這時候網頁還沒加載完,JavaScript那邊已經調用交互方法,這樣就會調不到原生應用的方法而出現問題。
改爲在- (void)viewDidLoad中去注入交互對象,這樣卻是解決了上面的問題,可是同時又引發了一個新的問題就是在一個網頁內部點擊連接跳轉到另外一個網頁的時候,第二個頁面須要交互,這時JSContext環境已經變化,可是- (void)viewDidLoad僅僅加載一次,跳轉的時候,沒有再次注入交互對象,這樣就會致使第二個頁面無法進行交互。固然你能夠在- (void)viewDidLoad和- (void)webViewDidFinishLoad:(UIWebView *)webView都注入一次,可是必定會有更優雅的辦法去解決此問題。
若是上邊的方案能知足需求,建議實在無可奈何再用這個方法, 就是在每次建立JSContext環境的時候,咱們都去注入此交互對象這樣就解決了上面的問題。具體解決辦法參考了此開源庫UIWebView-TS_JavaScriptContext。
1
2
3
4
5
6
7
|
NSArray *frames = [webView valueForKeyPath:@"documentView.webView.mainFrame.childFrames"];
[frames enumerateObjectsUsingBlock:^(id frame, NSUInteger idx, BOOL *stop) {
JSContext *context = [frame valueForKeyPath:@"javaScriptContext"];
context[@"Window"][@"prototype"][@"alert"] = ^(NSString *message) {
NSLog(@"%@", message);
};
}];
|
html中的JS代碼
1
|
<input type="button" value="多參數調用" onclick="mutiParams('參數1','參數2','參數3');" />
|
iOS中的代碼 UIWebview的delegate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// 以 html title 設置 導航欄 title
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
// Undocumented access to UIWebView's JSContext
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 打印異常
self.context.exceptionHandler =
^(JSContext *context, JSValue *exceptionValue)
{
context.exception = exceptionValue;
NSLog(@"%@", exceptionValue);
};
// 以 JSExport 協議關聯 native 的方法
self.context[@"app"] = self;
// 以 block 形式關聯 JavaScript function
self.context[@"log"] =
^(NSString *str)
{
NSLog(@"%@", str);
};
//多參數
self.context[@"mutiParams"] =
^(NSString *a,NSString *b,NSString *c)
{
NSLog(@"%@ %@ %@",a,b,c);
};
}
|
JSExport 協議關聯 native 的方法
Objective-C
1
|
@interface JSCallOCViewController : UIViewController<UIWebViewDelegate,TestJSExport>
|
1
2
3
4
5
6
7
|
- (void)pushViewController:(NSString *)view title:(NSString *)title
{
Class second = NSClassFromString(view);
id secondVC = [[second alloc]init];
((UIViewController*)secondVC).title = title;
[self.navigationController pushViewController:secondVC animated:YES];
}
|
JavaScript
1
|
<a id="push" href="#" onclick="app.pushViewControllerTitle('SecondViewController','secondPushedFromJS');">
|
Objective-C
調用js的showResult方法,這裏是一個參數 result,多個就依次寫到數組中
1
|
[self.context[@"showResult"] callWithArguments:@[result]];
|
JavaScript
1
2
3
4
|
function showResult(resultNumber)
{
document.getElementById("result").innerText = resultNumber;
}
|
iOS 8引入了一個新的框架——WebKit,以後變得好起來了。在WebKit框架中,有WKWebView能夠替換UIKit的UIWebView和AppKit的WebView,並且提供了在兩個平臺能夠一導致用的接口。WebKit框架使得開發者能夠在原生App中使用Nitro來提升網頁的性能和表現,Nitro就是Safari的JavaScript引擎 WKWebView 不支持JavaScriptCore的方式但提供message handler的方式爲JavaScript 與Native通訊.
1.Objective-C 調用JavaScript
1
2
3
4
5
6
|
//執行html 已經存在的js方法
- (IBAction)exeFuncTouched:(id)sender {
[self.myWebView evaluateJavaScript:@"showAlert('hahahha')" completionHandler:^(id item, NSError * _Nullable error) {
}];
}
|
JavaScript,簡單的封裝一下,‘Native’爲事先在Objective-C註冊注入的js對象
1
2
3
4
5
6
7
8
|
function callOC(func,param){
var url= "func=" + func;
for(var i in param)
{
url = url + "&" + i + "=" + param[i];
}
window.webkit.messageHandlers.Native.postMessage(url);
}
|
JavaScript調用
1
|
<input type="button" value="打個招呼" onclick="callOC('alert',{'message':'你好麼'})" />
|
Objective-C實現
1
2
3
4
5
6
7
8
9
|
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
// 注入JS對象Native,
// 聲明WKScriptMessageHandler 協議
[config.userContentController addScriptMessageHandler:self name:@"Native"];
self.myWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
self.myWebView.UIDelegate = self;
[self.view addSubview:self.myWebView];
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"Native"]) {
NSLog(@"message.body:%@", message.body);
//若是是本身定義的協議, 再截取協議中的方法和參數, 判斷無誤後在這裏手動調用oc方法
NSMutableDictionary *param = [self queryStringToDictionary:message.body];
NSLog(@"get param:%@",[param description]);
NSString *func = [param objectForKey:@"func"];
//調用本地函數
if([func isEqualToString:@"alert"])
{
[self showMessage:@"來自網頁的提示" message:[param objectForKey:@"message"]];
}
}
}
|
注:本文除了第三種方法以外,前兩種JavaScript交互方法都和Android開發兼容,僅僅是api略不一樣。
demo地址: https://github.com/shaojiankui/iOS-WebView-JavaScript