序言:
在iOS开发中,已经多次与js进行交互。这里系统的做一下整理,着重从三个方面进行阐述:
- UIWebView和WKWebView的对比
- 原生与js互相调用的几种方案
- 开发中遇到的一些坑
正文:
UIWebView(iOS2)和WKWebView(iOS8)的对比:
1.内存占用是UIWebView的1/4~1/3 ;
2.页面加载速度有提升,有的文章说它的加载速度比UIWebView提升了一倍左右;
3.更为细致地拆分了 UIWebViewDelegate 中的方法;
4.自带进度条。不需要像UIWebView一样自己做假进度条(通过NJKWebViewProgress和双层代理技术实现),技术复杂度和代码量,根贴近实际加载进度优化好的多;
5.允许JavaScript的Nitro库加载并使用(UIWebView中限制);
6.可以和js直接互调函数,不像UIWebView需要第三方库;WebViewJavascriptBridge来协助处理和js的交互;
7.不支持页面缓存,需要自己注入cookie,而UIWebView是自动注入cookie;
8.无法发送POST参数问题。
原生与js互相调用的几种方案:
UIWebView与JS的交互方式
- 原生调用js
- OC代码:
[self.webView stringByEvaluatingJavaScriptFromString:@"add(1,2)"];
- js代码:
function add(a,b) {
return a+b;
}
- js调用原生
方案一
iOS与JS交互之UIWebView-协议拦截
- OC代码:
//让Native 代码拦截,
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest:)request navigationType:(UIWebViewNavigationType)navigationType{
NSString *schem = webView.request.URL.scheme;
if ([schem containsString:@"jsCallBack://"]) {
//action...
return NO;
}
}
- js代码:
function btnClick1() {
location.href = "jsCallBack://method_?param1¶m2"
}
方案二
UIWebView-JavaScriptCore
- OC代码:
//! 导入JavaScriptCore框架头文件
#import
#pragma mark - UIWebViewDelegate
//! UIWebView在每次加载请求完成后会调用此方法
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//! 获取JS代码的执行环境/上下文/作用域
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//! 监听JS代码里面的jsToOc方法(执行效果上可以理解成重写了JS的jsToOc方法)
context[@"jsToOc"] = ^(NSString *action, NSString *params) {
dispatch_async(dispatch_get_main_queue(), ^{
[UIWebViewJavaScriptCoreController showAlertWithTitle:action message:params cancelHandler:nil];
});
};
}
- js代码:
同下面WKWebView中js调用OC里的 方法二 的js代码一样。
方案三
UIWebView-JSExport协议
- OC代码:
//! 导入JavaScriptCore框架头文件
#import
@protocol OCJSExport
//! 为OC的-jsToOC:params:方法起个JS认识的别名jsToOc
JSExportAs(jsToOc, - (void)jsToOc:(NSString *)action params:(NSString *)params);
@end
//! UIWebViewJSExportController遵守OCJSExport协议
@interface UIWebViewJSExportController ()
#pragma mark - JSExport functions
//! 实现OCJSExport协议的方法
- (void)jsToOc:(NSString *)action params:(NSString *)params {
dispatch_async(dispatch_get_main_queue(), ^{
[UIWebViewJSExportController showAlertWithTitle:action message:params cancelHandler:nil];
});
}
#pragma mark - UIWebViewDelegate
//! UIWebView在每次加载请求完成后会调用此方法
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//! 获取JS代码的执行环境/上下文/作用域
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//! 在context注册OCJSBridge对象为self
context[@"OCJSBridge"] = self;//!< 有循环引用问题
}
- js代码:
//! 登录
function login() {
var token = "js_tokenString";
loginSucceed(token);
}
//! 登录成功
function loginSucceed(token) {
var action = "loginSucceed";
OCJSBridge.jsToOc(action, token);
}
WKWebView与JS的交互方式
- 原生调用js
- OC代码:
[self.wkWebView evaluateJavaScript:@"playSount()" completionHandler:nil];
- js代码:
function playSount() {
//playSount...
}
- js调用原生
方案一:
WKWebView-WKScriptMessageHandler
- OC代码:
1)在创建wkWebView时,需要将被js调用的方法注册进去
//创建WKWebViewConfiguration文件
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.preferences.minimumFontSize = 10.f;
[config.userContentController addScriptMessageHandler:self name:@"playSound"];
//创建WKWebView类
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
2)在WKScriptMessageHandler代理方法中监听js的调用
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"playSound"]) {
[self playSound];
}
}
- js代码:
//JS响应事件
function btnClick() {
window.webkit.messageHandlers.playSound.postMessage(null);
}
//js各种情况代码示例:
// 空的时候null 啥都不写的话会有问题
function scanClick() {
window.webkit.messageHandlers.ScanAction.postMessage(null);
}
// 传字典
function shareClick() {
window.webkit.messageHandlers.Share.postMessage({title:'测试分享的标题',content:'测试分享的内容',url:'http://www.baidu.com'});
}
// 传字符串
function playSound() {
window.webkit.messageHandlers.PlaySound.postMessage('shake_sound_male.wav');
}
// 传数组
function colorClick() {
window.webkit.messageHandlers.Color.postMessage([67,205,128,0.5]);
}
方案二:
WKWebView-协议拦截
- OC代码:
#pragma mark - WKNavigationDelegate
//! WKWeView在每次加载请求前会调用此方法来确认是否进行请求跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if ([navigationAction.request.URL.scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
[WKWebViewInterceptController showAlertWithTitle:navigationAction.request.URL.host message:navigationAction.request.URL.query cancelHandler:nil];
decisionHandler(WKNavigationActionPolicyCancel);
}
else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
- js代码:
//! 登录
function login() {
var token = "js_tokenString";
loginSucceed(token);
}
//! 登录成功
function loginSucceed(token) {
var action = "loginSucceed";
jsToOc(action, token);
}
//! JS调用OC入口
function jsToOc(action, params) {
var url = "jsToOc://" + action + "?" + params;
loadURL(url);
}
//! 加载URL
function loadURL(url) {
window.location.href = url;
}
UIWebView 默认会显示js弹窗,WKWebView要想显示js弹窗的话,需要实现下面代理方法:
#pragma mark - WKUIDelegate
//! alert(message)
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
}
//! confirm(message)
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Confirm" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}];
[alertController addAction:cancelAction];
[alertController addAction:confirmAction];
[self presentViewController:alertController animated:YES completion:nil];
}
//! prompt(prompt, defaultText)
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = defaultText;
}];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text);
}];
[alertController addAction:confirmAction];
[self presentViewController:alertController animated:YES completion:nil];
}
这里还有一个三方WebViewJavascriptBridge,UIWebView和WKWebVIew都可以用,用起来也是非常的方便
UIWebView与JS交互遇到的一些坑
- iOS9白屏问题 解决方案
- WKWebView 存储cookie,和UIWebView是不一样的 cookie相关
结束语:
望见伊人,在水一方
我顺流而下,或逆流而上
不畏三千里河长,不惧万重山阻挡
参考文章:
iOS与JS交互之WKWebView-协议拦截
iOS UIWebView 与WKWebView集锦