WKWebView是苹果在iOS8中引入的新组件,目的是给出一个新的高性能的WebView解决方案,摆脱过去 UIWebView 的老旧笨重特别是内存占用量巨大的问题,本文主要是想讨论使用iOS控件WKWebView通过中间页加载H5 SDK,并且会根据企业开发需求配置不同环境(测试、线上、预发)下的H5 SDK,在讨论这个问题之前会稍带啰嗦总结下WKWebView的加载方式和基本API
WKWebView加载H5方式
1. 通过url地址加载网络资源
- (void)loadWithUrl{
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com/"]]];
[self.view addSubview:self.webView];
}
2.通过html加载本地资源
html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>iOS and JS</title>
<style type="text/css">
* {
font-size: 40px;
}
</style>
</head>
<body>
<div style="margin-top: 10px">
<h1 style="color: red;">加载本地html文件</h1><br/>
</div>
</body>
</html>
加载函数
- (void)loadWithHtml {
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
NSURL *path = [[NSBundle mainBundle] URLForResource:@"text" withExtension:@"html"];
[self.webView loadRequest:[NSURLRequest requestWithURL:path]];
[self.view addSubview:self.webView];
}
3.直接加载html代码
- (void)loadWithHtmlStr {
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
//注意:" 要用 \"代替
NSString *HTMLData = @"<h1 style = \"color: red;\">直接加载本地htmlstr</h1>";
[self.webView loadHTMLString:HTMLData baseURL:nil];
[self.view addSubview:self.webView];
}
WKWebView基本API
WKWebView的基本API在blog.csdn.net/baihuaxiu12… 已经介绍的很详细了,这里不做过多赘述,仅挑比较常用的进行说明。
WKUserContentController内容交互控制器
// 只读属性,所有添加的WKUserScript都在这里可以获取到
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
// 注入JS
- (void)addUserScript:(WKUserScript *)userScript;
// 移除所有注入的JS
- (void)removeAllUserScripts;
// 添加scriptMessageHandler到所有的frames中,则都可以通过
// window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
// 发送消息
// 比如,JS要调用我们原生的方法,就可以通过这种方式了
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
// 根据name移除所注入的scriptMessageHandler
- (void)removeScriptMessageHandlerForName:(NSString *)name;
我们可以在创建WKWebView的时候为其config.userContentController添加handler,并且在遵守WKScriptMessageHandler前提下可以在协议方法- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{}进行监听
WKScriptMessageHandler
@protocol WKScriptMessageHandler <NSObject>
@required
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
@end
结合例子
OC中:
config.userContentController = [[WKUserContentController alloc]init];
[config.userContentController addScriptMessageHandler:self name:@"AppModel"];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
JS中:
<script type="text/javascript">
function text(option) {
window.webkit.messageHandlers.AppModel.postMessage("向OC中传递的数据");
}
</script>
OC中协议监听:
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"AppModel"]) {
NSLog(@"回调监听 === %@", message.body); // 打印结果:向OC中传递的数据
}
}
在上述的小列子中,在WKWebView进行实例化时添加了name为"AppModel"的handler,一旦在JS中触发了text方法,js通过window.webkit.messageHandlers.AppModel.postMessage()就可以给OC传递消息,在OC端的监听方法中可以对其进行处理。
WKNavigationDelegate
@protocol WKNavigationDelegate <NSObject>
@optional
// 决定导航的动作,通常用于处理跨域的链接能否导航。WebKit对跨域进行了安全检查限制,不允许跨域,因此我们要对不能跨域的链接
// 单独处理。但是,对于Safari是允许跨域的,不用这么处理。
// 这个是决定是否Request
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
// 决定是否接收响应
// 这个是决定是否接收response
// 要获取response,通过WKNavigationResponse对象获取
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 当main frame的导航开始请求时,会调用此方法
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 当main frame接收到服务重定向时,会回调此方法
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 当main frame开始加载数据失败时,会回调
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 当main frame的web内容开始到达时,会回调
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
// 当main frame导航完成时,会回调
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
// 当main frame最后下载数据失败时,会回调
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 这与用于授权验证的API,与AFN、UIWebView的授权验证API是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
// 当web content处理完成时,会回调
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
@end
在代理协议WKNavigationDelegate中使用最多的还是didFinishNavigation:可以在导航加载完成时,在此回调配合evaluateJavaScript:去主动调用JS的一些方法.
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
// 直接执行text函数
[self.webView evaluateJavaScript:@"text()" completionHandler:^(id _Nullable res, NSError * _Nullable error) {
NSLog(@"respose = %@ -- error = %@", res, error);
}];
//执行text函数并带入参数
NSString *text = [NSString stringWithFormat:@"text('%@')",self.userId];
[self.webView evaluateJavaScript:text completionHandler:^(id _Nullable res, NSError * _Nullable error) {
NSLog(@"respose = %@ -- error = %@", res, error);
}];
}
WKWebView中间页加载H5 SDK
说了这么多终于来到了我们要讨论的问题,在OC的开发中避免不了需要在OC中展示H5页面和进行一些交互。一般企业推出的SDK都会包含了iOS、Android、和H5的三种接入方式,其中H5不仅可以适用于web也可以用iOS和Android来接入。在此例子中使用[网易七鱼]qiyukf.com/docs/guide/… 在iOS中接入H5 SDK的客服咨询聊天功能为例(此例子仅作接入思路参考,真正开发中不建议这么做,毕竟接入H5 SDK 会散失一部分原本属于SDK的功能,比如它的客服消息提示等)
在上述例子中的SDK可以使用url和本地html文件的方式接入,但是url地址进行拼接的一些参数又是无效的(匿名用户),只能排除,使用html文件的接入方式,一般开发中会根据不同环境(开发、测试)来进行初始化SDK。我们可以在html文件中定义一个js方法,触发方法时带着一些必需的参数进行初始化
html:
function register(isDev, appkey, uid, name){
var url;
if (isDev) { // 不同环境域名不相同
url = `https://aaa.aaa.aaa/script/${appkey}.js?hidden=1`;
} else {
url = `https://bbb.bbb.bbb/script/${appkey}.js?hidden=1`;
}
//初始化
(function (w, d, n, a, j) {
w[n] = w[n] || function () {
(w[n].a = w[n].a || []).push(arguments);
};
j = d.createElement('script');
j.async = true;
j.src =`${url}`;
d.body.appendChild(j);
})(window, document, 'ysf');
//上报用户信息
ysf('config', {
uid:`${uid}`,
name:`${name}`,
data:JSON.stringify(xxx),
success: function() {
window.webkit.messageHandlers.AppModel.postMessage('true');
},
error: function() {
window.webkit.messageHandlers.AppModel.postMessage('fase');
}
});
}
在上述的html文件中使用传参isDev来区分开发环境还是线上环境,传参appkey来初始化sdk,uid和name来配置上报用户信息
- (NSString *)javascriptStr {
NSString *domain = xxx;
NSString *account = xxx;
//线上环境
if (setting == Online) {
return [NSString stringWithFormat:@"register(0,'%@','%@','%@')",kOnlineAppKey, domain, account];
}
//测试环境
if (setting == Test) {
return [NSString stringWithFormat:@"register(1,'%@','%@','%@')",kTestAppKey, domain, account];
}
//预发环境
if (setting == OnlinePre) {
return [NSString stringWithFormat:@"register(0,'%@','%@','%@')",kPreAppKey, domain, account];
}
return @"";
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
// 拿到不同环境的配置字符串
NSString *javascriptStr = [self javascriptStr];
if (![NSString isEmptyStr:javascriptStr]) {
//执行JS代码
[self.webView evaluateJavaScript:javascriptStr completionHandler:^(id _Nullable res, NSError * _Nullable error) {
// NSLog(@"res = %@ -- error = %@", res, error);
}];
} else {
NSLog(@"Toast提示")
}
}
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"AppModel"]) {
if ([message.body isEqualToString:@"true"]) {
NSLog(@"初始化上报成功") ---(执行OC或者JS代码等操作)
} else {
NSLog(@"初始化上报失败") ---(Toast提示等操作)
}
}
}
在上述的OC文件中在中间页加载完成之后调用evaluateJavaScript:来执行html的JS代码进行初始化和上报用户信息等操作,操作执行后的回调在WKScriptMessageHandler中监听,对应的成功失败在进行其他的业务逻辑处理。
总结
WKWebView作为OC侧用来展示交互H5重要控件,包含了众多的代理方法可以很好的执行JS代码和感知JS操作后的回调,十分方便灵活,需要注意的是在执行本地html文件时,需要注意本地文件读取和跨域的问题。