iOS高階-WebView & JavaScript互動(附DEMO)
文中程式碼與html地址在 LJMElevateSelf in GitHub
WebView與JavaScript的互動方式
0.iOS7之前,原生與JavaScript的互動
1.JavaScriptCore(適用於UIWebView,iOS 7+)
2.WKScriptMessageHandler(適用於WKWebView,iOS 8+)
3.攔截協議(適用於UIWebView和WKWebView)
4.WebViewJavascriptBridge(適用於UIWebView和WKWebView的第三方框架,是基於攔截協議進行的封裝)
0. iOS7之前,原生與JavaScript的互動
原生 -> JavaScript
//UIWebView的方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
stringByEvaluatingJavaScriptFromString:
只能在主執行緒執行;
通過此方法可以簡單呼叫系統提供的Javascript方法。例如:
// 獲取當前頁面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
// 獲取當前頁面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];
JavaScript -> 原生
遵守UIWebViewDelegate並實現代理方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
該方法可以監聽到UIWebView中發出的URL請求,通過與H5協商一個URL通訊協議,來攔截指定的URL,做相應的操作,並阻止此連結的跳轉。舉個例子:
html程式碼:
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
</head><body>
<div style="margin-top: 10px">
<input type="button" value="callPhone" onclick="callPhone()">
</div>
</body><script>
// 宣告一個名為callPhone的js函式,其會發出一個連結為nativejs://callPhone的請求
function callPhone() {
window.location.href = 'nativejs://callPhone';
}
</script></html>
objc程式碼:
/**
* 在一個網頁開始載入一個frame前被呼叫
*/
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString *urlString = request.URL.absoluteString;
NSRange range = [urlString rangeOfString:@"nativejs://"];
if (range.location != NSNotFound) { // 攔截URL協議頭是nativejs的連結
NSLog(@"執行原生呼叫的方法");
return NO;// 阻止此連結的跳轉
}
return YES;
}
由於絕大多數APP已經支援iOS 7+,所以這裡只做簡單介紹。
1. JavaScriptCore
JavaScriptCore是iOS 7之後蘋果推出的框架可以讓互動變的更加方便。舉個例子:
JavaScriptCore.html內容:
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
</head><body>
<div style="margin-top: 100px">
<h1>JavaScriptCore的簡單介紹與使用</h1>
<input type="button" value="呼叫相機" onclick="LJMcarryu.callCamera()">
</div>
<div>
<input type="button" value="分享" onclick="callShare()">
</div>
<script>
var callShare = function() {
var shareInfo = JSON.stringify({"title": "標題",
"description": "內容",
"shareUrl": "https://www.jianshu.com/p/afbd98793c18",
"shareImage":"http://a2.att.hudong.com/46/18/01300000309266122822186737333.jpg"
});
LJMcarryu.share(shareInfo);
}
var picCallback = function(photos) {
alert(photos);
}
var shareCallback = function(){
alert('分享成功');
}
</script>
</body></html>
上述程式碼比較簡單,定義兩個按鈕 “呼叫相機”、“分享” 其中“分享”傳有引數shareInfo
;並在下方定義了兩個原生方法呼叫後的回撥方法picCallback
、shareCallback
客戶端這邊根據web前端定義的方法名去完成互動,JavaScriptCore中web頁面呼叫原生應用的方法可以用Delegate
或Block
兩種方法,示例以Delegate
來講。
Objc程式碼:
#import "BlocksKitViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSOBJCDelegate <JSExport>
- (void)callCamera;
- (void)share:(NSString *)shareString;
@end
@interface BlocksKitViewController () <UIWebViewDelegate, JSOBJCDelegate>
@property (nonatomic, strong) JSContext *jsContext;
@end
@implementation BlocksKitViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
webView.delegate = self;
[self.view addSubview:webView];
NSURL *url = [[NSBundle mainBundle] URLForResource:@"TexstJavaScriptCore" withExtension:@"html"];
[webView loadRequest:[[NSURLRequest alloc] initWithURL:url]];
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(@"網頁載入完成");
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.jsContext[@"LJMcarryu"] = self;
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"異常資訊:%@", exceptionValue);
};
}
#pragma mark - JSObjcDelegate
- (void)callCamera {
NSLog(@"呼叫相機");
// 獲取到照片之後在回撥js的方法picCallback把圖片傳出去
JSValue *picCallback = self.jsContext[@"picCallback"];
[picCallback callWithArguments:@[@"這是圖片"]];
}
- (void)share:(NSString *)shareString {
NSLog(@"分享內容:%@", shareString);
// 分享成功回撥js的方法shareCallback
JSValue *shareCallback = self.jsContext[@"shareCallback"];
[shareCallback callWithArguments:nil];
}
在介紹程式碼內容前,先來看看JavaScriptCore的幾種類與協議:
JSExport
:JavaScriptCore裡的協議,如果採用協議的方法進行互動,自定義的協議就必須遵守此協議;JSContext
:給JavaScript提供執行的上下文環境;JSValue
:JavaScript和Objective-C資料和方法的橋樑;JSManagedValue
:管理資料和方法的類;JSVirtualMachine
:處理執行緒相關
上述程式碼中:
自定義的JSOBJCDelegate
必須遵守JSExport
協議。
在webView
載入完成後獲取JavaScript執行的上下文環境,然後再注入物件名為LJMcarryu
的橋樑,物件為self
,self
遵守此自定義協議並實現協議中對應的方法。
在JavaScript呼叫完本地方法做完相對應的事情之後,又回撥了JavaScript中對應的方法,從而實現了web頁面和本地應用之間的通訊。
2. WKScriptMessageHandler
這裡糾結是直接寫互動部分,還是比較系統的介紹WKWebView,回頭補上
3. 攔截協議
攔截協議使用時不需引入框架,只用於一些簡單的情況,因為其無法回撥JavaScript的方法
InterceptUrl.html程式碼:
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
</head><body>
<div>
<input type="button" value="呼叫相機" onclick="callCamera()">
</div>
<script>
function callCamera() {
//改變主視窗指向併發出請求
window.location.href = 'LJMcarryu://callCamera';
}
</script>
</body></html>
非常簡單的頁面,只有一個按鈕來呼叫相機。
客戶端要攔截LJMcarryu://callCamera
請求,根據請求內容來完成JavaScript想做的事情。
Objc程式碼:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = request.URL.absoluteString;
if ([url rangeOfString:@"LJMcarryu://"].location != NSNotFound) {
// 攔截LJMcarryu://,阻止此連結的跳轉,返回NO
NSLog(@"呼叫相機成功");
return NO;
}
return YES;
}
4. WebViewJavascriptBridge
WebViewJavascriptBridge是基於攔截協議的封裝,讓其能夠較好的用於 WKWebView & UIWebView 中 OC 和 JS 的互動。也是舉個例子:
html檔案就借鑑WebViewJavascriptBridge自身demo裡的ExampleApp.html,做了簡單的修改:
<!doctype html>
<html><head>
<meta charset="UTF-8" name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style type='text/css'>
html { font-family:Helvetica; color:#222; }
h1 { color:steelblue; font-size:24px; margin-top:24px; }
button { margin:0 3px 10px; font-size:12px; }
.logLine { border-bottom:1px solid #ccc; padding:4px 2px; font-family:courier; font-size:11px; }
</style>
</head><body>
<h1>WebViewJavascriptBridge Demo</h1>
<script>
window.onerror = function(err) {
log('window.onerror: ' + err)
}
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
//這一段程式碼是註冊OC將要呼叫的JS方法
setupWebViewJavascriptBridge(function(bridge) {
var uniqueId = 1
function log(message, data) {
var log = document.getElementById('log')
var el = document.createElement('div')
el.className = 'logLine'
el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
if (log.children.length) { log.insertBefore(el, log.children[0]) }
else { log.appendChild(el) }
}
//testJavascriptHandler01 是OC呼叫JS方法的函式名
//data OC傳遞過來的資料
//responseCallback 向OC傳遞的資料
bridge.registerHandler('testJavascriptHandler01', function(data, responseCallback) {
log('原生呼叫JS處理', data)
var responseData = { 'JS說':'OK,I‘m ready!' }
log('JS回應道', responseData)
responseCallback(responseData)
})
//testJavascriptHandler02 也是OC呼叫JS方法的函式名
//data OC傳遞過來的資料
//responseCallback 向OC傳遞的資料
bridge.registerHandler('testJavascriptHandler02', function(data, responseCallback) {
log('原生呼叫JS處理', data)
var responseData = { 'JS說':'OK,I see you!' }
log('JS回應道', responseData)
responseCallback(responseData)
})
document.body.appendChild(document.createElement('br'))
var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
callbackButton.innerHTML = '啟用原生回撥'
callbackButton.onclick = function(e) {
e.preventDefault()
log('JS喚醒處理原生回撥','原生應我一聲')
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
log('JS獲得回應', response)
})
}
})
</script>
<div id='buttons'></div> <div id='log'></div>
</body></html>
上述程式碼改成了中文輸出,並加了一個橋樑註冊,完整程式碼 LJMElevateSelf in GitHub
Objc程式碼:
#import "WebViewJavascriptBridgeViewController.h"
#import "WebViewJavascriptBridge.h"
@interface WebViewJavascriptBridgeViewController ()
@property WebViewJavascriptBridge *bridge;
@end
@implementation WebViewJavascriptBridgeViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (_bridge) return;
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
webView.navigationDelegate = self;
[self.view addSubview:webView];
//UIWebView
// UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
// [self.view addSubview:webView];
[WebViewJavascriptBridge enableLogging];
_bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
[_bridge setWebViewDelegate:self];
/*
data 是JS傳遞過來的資料
responseCallback 往JS傳遞的資料
*/
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"原生回撥呼叫內容: %@", data);
responseCallback(@"來自原生回撥的回應");
}];
[_bridge callHandler:@"testJavascriptHandler01" data:@{ @"提醒訊息":@"JS做好準備" }];
[self renderButtons:webView];
[self loadExamplePage:webView];
}
- (void)renderButtons:(WKWebView*)webView {
UIFont* font = [UIFont fontWithName:@"HelveticaNeue" size:12.0];
UIButton *callbackButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[callbackButton setTitle:@"呼叫處理" forState:UIControlStateNormal];
[callbackButton addTarget:self action:@selector(callHandler:) forControlEvents:UIControlEventTouchUpInside];
[self.view insertSubview:callbackButton aboveSubview:webView];
callbackButton.frame = CGRectMake(10, 400, 100, 35);
callbackButton.titleLabel.font = font;
UIButton* reloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[reloadButton setTitle:@"重載入檢視" forState:UIControlStateNormal];
[reloadButton addTarget:webView action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
[self.view insertSubview:reloadButton aboveSubview:webView];
reloadButton.frame = CGRectMake(110, 400, 100, 35);
reloadButton.titleLabel.font = font;
//UIWebView
// UIButton *safetyTimeoutButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [safetyTimeoutButton setTitle:@"Disable safety timeout" forState:UIControlStateNormal];
// [safetyTimeoutButton addTarget:self action:@selector(disableSafetyTimeout) forControlEvents:UIControlEventTouchUpInside];
// [self.view insertSubview:safetyTimeoutButton aboveSubview:webView];
// safetyTimeoutButton.frame = CGRectMake(190, 400, 120, 35);
// safetyTimeoutButton.titleLabel.font = font;
}
//UIWebView
//- (void)disableSafetyTimeout {
// [self.bridge disableJavscriptAlertBoxSafetyTimeout];
//}
- (void)callHandler:(id)sender {
id data = @{ @"來自原生的問候": @"嗨,我在這 JS!" };
[_bridge callHandler:@"testJavascriptHandler02" data:data responseCallback:^(id response) {
NSLog(@"JS處理回應: %@", response);
}];
}
- (void)loadExamplePage:(WKWebView*)webView {
NSString* htmlPath = [[NSBundle mainBundle] pathForResource:@"ExampleApp" ofType:@"html"];
NSString* appHtml = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
NSURL *baseURL = [NSURL fileURLWithPath:htmlPath];
[webView loadHTMLString:appHtml baseURL:baseURL];
}
//WKWebView
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"網頁開始載入");
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSLog(@"網頁載入完成");
}
//UIWebView
//- (void)webViewDidStartLoad:(UIWebView *)webView {
// NSLog(@"網頁開始載入");
//}
//- (void)webViewDidFinishLoad:(UIWebView *)webView {
// NSLog(@"網頁載入完成");
//}
程式碼解釋就不說了,多執行檢查幾遍,自行理解與吸收
另外:
OC -> JS
// 單純的呼叫 JSFunction,不往 JS 傳遞引數,也不需要 JSFunction 的返回值。
[_bridge callHandler:@"testJavascriptHandler"];
// 呼叫 JSFunction,並向 JS 傳遞引數,但不需要 JSFunciton 的返回值。
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"提醒訊息":@"JS做好準備" }];
// 呼叫 JSFunction ,並向 JS 傳遞引數,也需要 JSFunction 的返回值。
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"提醒訊息":@"JS做好準備" } responseCallback:^(id responseData) {
NSLog(@"JS 的返回值: %@",responseData);
}];
JS -> OC
// JS 單純的呼叫 OC 的 block
WebViewJavascriptBridge.callHandler('shareClick');
// JS 呼叫 OC 的 block,並傳遞 JS 引數
WebViewJavascriptBridge.callHandler('shareClick',"JS 引數");
// JS 呼叫 OC 的 block,傳遞 JS 引數,並接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('shareClick',{'foo': 'bar'},function(dataFromOC){
alert("JS 呼叫了 OC 的分享方法!");
document.getElementById("returnValue").value = dataFromOC;
});
上述所有程式碼在LJMElevateSelf in GitHub
Home資料夾下的BlocksKitViewController與WebViewJavascriptBridgeViewController;
html檔案在Networks檔案下。
參考連結:
WebViewJavascriptBridge in GitHub
Objective-C與JavaScript互動的那些事
相關文章
- Android 原生 WebView 與 JavaScript 互動AndroidWebViewJavaScript
- Android Webview Java和Javascript安全互動AndroidWebViewJavaScript
- Android WebView —— Java 與 JavaScript 互動總結AndroidWebViewJavaScript
- 高階前端進階系列 - webview前端WebView
- iOS開發-javaScript互動iOSJavaScript
- Android混合開發之WebView與Javascript互動AndroidWebViewJavaScript
- Android webview JS 互動AndroidWebViewJS
- webview與JS的互動WebViewJS
- 最全面總結 Android WebView與 JS 的互動方式(含例項Demo)AndroidWebViewJS
- iOS 策略模式附DemoiOS模式
- iOS環信整合(附demo)iOS
- android 的webView載入h5,和h5的互動(java和JavaScript的互動)AndroidWebViewH5JavaScript
- Android webview 與 js(Vue) 互動AndroidWebViewJSVue
- Android與WebView資料互動AndroidWebView
- Python高階 -- 08 MySQL與Python互動PythonMySql
- 使用 JSBridge 與原生 IOS、Android 進行互動(含 H5、Android、IOS 端程式碼,附 Demo)JSiOSAndroidH5
- JavaScript 高階技巧JavaScript
- App自動化測試:高階控制元件互動技巧APP控制元件
- Flutter使用JsBridge與WebView互動FlutterJSWebView
- Flutter WebView與JS互動簡易指南FlutterWebViewJS
- WebView與APP互動實戰記錄WebViewAPP
- WKWebView和WebView與JS的互動方式WebViewJS
- Javascript 高階函式JavaScript函式
- RN互動iOSiOS
- WebView與JS的互動,以及注意事項WebViewJS
- 記一則iOS封裝SDK的開發過程之WebView與JS的互動iOS封裝WebViewJS
- 《前端之路》之 JavaScript 高階技巧、高階函式(一)前端JavaScript函式
- iOS 實現自動登入(從低階做法到高階做法)iOS
- 常用JavaScript的高階技巧JavaScript
- JavaScript高階特性 — 作用域JavaScript
- javaScript高階級函式JavaScript函式
- javascript的高階應用JavaScript
- iOS javascript與object-c的互動(TSY版本-就是本人)iOSJavaScriptObject
- ReactNative iOS 互動ReactiOS
- WebView和js的互調WebViewJS
- iOS 核心動畫高階技巧 - 1iOS動畫
- iOS 藍芽4.0開發使用(內附Demo)iOS藍芽
- react native ScrollView巢狀WebView 互動問題React Native巢狀WebView