一、配置
1、初始化
导入头文件#import<WebKit/WebKit.h>
#pragma mark - LazyLoad
- (WKWebView *)webView {
if (!_webView) {
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:handler name:@"XXX"];//对象名称
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = userContentController;
_webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
_webView.navigationDelegate = self;
_webView.UIDelegate = self;
_webView.backgroundColor = [UIColor clearColor];
_webView.allowsBackForwardNavigationGestures = YES;
_webView.scrollView.alwaysBounceVertical = YES;
}
return _webView;
}
2、delegate
WKNavigationDelegate:
和UIWebViewDelegate功能类似,提供加载过程操作、跳转等功能
// 开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载完调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error ;
// 内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation ;
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 加载 HTTPS 的链接,需要权限认证时调用 \ 如果 HTTPS 是用的证书在信任列表中这不要此代理方法
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
WKUIDelegate:
与UI界面相关,通过此delegate调用原生提示框比较常用
#pragma mark - WKUIDelegate
// 提示框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler ;
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler ;
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
3、KVO
WKWebView可通过KVO监听属性 title、estimaredProgress来设置VC的title和进度条
#pragma mark - KVO
- (void)addObserverForWebView {
//实现显示HTML中写好的title
[self.webView addObserver:self
forKeyPath:@"title"
options:NSKeyValueObservingOptionNew
context:nil];
//实现加载过程中的进度条
[self.webView addObserver:self
forKeyPath:@"estimatedProgress"
options:NSKeyValueObservingOptionNew
context:nil];
}
二、加载
常用到的加载方式基本和UIWebview一样。如果项目Info.plist中设置了App Transport Security,最好是使用https,非https则需要添加白名单,外链也要添加白名单!
1、网络加载
//通过url加载网络资源
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
2、加载HTMLString
通常需要加载的HTMLString,一般是从服务器传过来显示,要么就是固定写死在代码里的。当然,本地的HTML文件也可以转成HTMLString。
//这里bundleUrl是mainBundle路径的url 如果是后台穿过来的htmlString直接传nil
[self.webView loadHTMLString:string baseURL:bundleUrl];
3、加载本地HTML文件
这一步就取决于你拿到手的文件了,如果只有一个对应的HTML文件,样式都写在HTML中,不包含CSS、js文件( 类似iOS 不使用mvc全丢在Controller里),直接丢项目里,然后获取到文件在本地的url加载就行了。
NSString *path =[[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
url = [NSURL fileURLWithPath:path];
如果还有.CSS、.js、图片资源等,那就需要考虑资源路径读取的问题,因为前端JS CSS 的调用有严格的页面结构,不然就会碰到图片和样式加载不出来的现象。
![我们常用的导入方式][id]
[id]: http://upload-images.jianshu.io/upload_images/1324353-bfe0baa5e41ab006.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&_=6197761/to/image "Optional title attribute"
Create groups 忽略文件目录(相对路径,不建议采用)
以Create groups方式加入工程的文件夹,文件夹下的文件在iOS沙盒中全都被保存在一个 mainBundle 根路径下,即不管加入项目的文件的目录结构如何,在 APP 中都可以通过 mainBundlePath/filename 来访问到,而原来的目录结构则不存在了。而 HTML 中的图片和 CSS 文件的引用方式写的则是绝对路径。因此 HTML 中的路径就不对,需要把HTML中引用文件代码也忽略掉文件目录。如下:
src="/public/comm/highcharts.min.js"
href="/public/css/reset.css"
src="/public/images/test@2x.png"
替换成忽略文件目录的样式
src="min.js"
href="reset.css"
src="ic_test@2x.png"
然后就可以进行加载了
NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
NSString *htmlString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSString *basePath = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:basePath];
[self.webView loadHTMLString:htmlString baseURL:baseURL]
这样替换的工作量可想而知,如果前端写的时候就是这么引用,而且无重名文件,那也是可以的。
Create folder references 引用按照文件目录(绝对路径)
以这种方式导入工程时,文件夹会是蓝色的,一眼瞟过去明显和其他文件不搭。但是丑归丑,问题好歹解决了。
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html" inDirectory:@"fileName/fileName"];
NSURL *fileUrl = [NSURL fileURLWithPath: htmlPath];
[self.webView loadRequest:[NSURLRequest requestWithURL:fileUrl]];
注意 HTML 文件路径一定要写对!
注意 HTML 文件路径一定要写对!
注意 HTML 文件路径一定要写对!
三、交互
1、传参
其实和JS交互都可以理解为传参,无非就是信号的传递,然后做出对应的处理。这里的传参指的是把参数拼接在url后面,类似get请求。一般都是比较简单的HTML需求。给网络HTML传参直接拼url就好,本地HTML稍微麻烦点,需要绕过fileURLWithPath:拼出URLString。
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];;
// NSURL *fileUrl = [NSURL fileURLWithPath: htmlPath];//内部实现加file:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@?minute=%ld", encodedPath, minute]];
2、原生调用JS方法
原生调用JS方法只需要传对应的string过去就可以了。
[self.webView evaluateJavaScript:string completionHandler:^(id _Nullable response, NSError * _Nullable error) {
//如果这里有error肯定是没成功
}];
这里有遇到一个坑,前端那边要求把参数转成json,可是回调回来就是不成功,后面找到原因是前端那边的json需要去空格去换行...可能是序列化和反序列化过程规则不一致把。
3、js调用原生方法
注入对象
通常JS需要调用原生方法时,需要在初始化WKWebview时注入对象。这里的handler最好是单独写个工具类出来,来灵活处理响应事件,给VC减负。
[userContentController addScriptMessageHandler:ocjsHelper name:@"XXX"];//对象名称
在dealloc方法中需要remove掉注册的对象,否则会造成内存泄漏。
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"XXX"];
这里、当handler为self时,会导致dealloc不执行、造成内存泄漏,单独注册一个handler并不会出现这个问题,暂时猜想可能是内部循环引用的问题(PS:只是猜想。。。)。
JS端发送消息
window.webkit.messageHandlers.showSendMsg.postMessage('sendMessage'])
接收消息
handler实现WKScriptMessageHandler协议
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
//name 注入对象名(string) ,body和前端商定的数据类型
if ([message.name isEqualToString:@"XXX"] && [message.body isKindOfClass:[NSDictionary class]]) {
//对应的业务处理
}
}
四、扩展
1、Html本地文件统一管理
当项目中的HTML文件越来越多,而且在项目中HTML文件都是以绝对路径的方式导入(黄蓝配),那么HTML文件就需要统一管理了。iOS中bundle就为我们提供了很好的解决方案,简而言之bundle一个目录就是用来存放项目中的各种资源文件,包括代码。新建bundle的优势在于你能方便的操作资源文件。制作bundle点击这里
//获取bundle的path
NSString *resourcesBundlePath = [[NSBundle mainBundle] pathForResource:@"TestBundle" ofType:@"bundle"];
//获取bundle路径下的文件path
NSString *htmlPath = [[NSBundle bundleWithPath:resourcesBundlePath] pathForResource:@"webLoaclFile" ofType:@"html" inDirectory:@"fileCatalogue"];
2、修改web显示样式
WKWebview和UIWebview类似都可以实现对web样式的一些简单修改。
//修改字体大小 300%
[self.webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '300%'" completionHandler:nil];
//修改字体颜色 #00bb00
[self.webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#00bb00'" completionHandler:nil];
3、HTML实现显示自定义字体
(1)如果是本地HTML,前端在文件内加入对应字体就行,不需要手机端再做处理。
(2)如果是从服务器获取,可以从服务器下载缓存,或者丢在本地,加载时把字体路径传给HTML也应该是可以的。
(3)字体丢在本地,然后再对获取的HTMLString进行处理也是可以的,应该比较麻烦。
4、WebViewJavascriptBridge
如果不想重复造轮子,可以试试这个库,支持WKWebview和UIWebview
cocoapods: pod 'WebViewJavascriptBridge', '~> 5.0.5'
github: https://github.com/marcuswestin/WebViewJavascriptBridge