wkwebview使用时需要导入(#import <WebKit/WebKit.h>)
WKWebView
从iOS8才有,毫无疑问WKWebView
将逐步取代笨重的UIWebView
。通过简单的测试即可发现UIWebView
占用过多内存,且内存峰值更是夸张。WKWebView
网页加载速度也有提升,但是并不像内存那样提升那么多。下面列举一些其它的优势:
- 更多的支持HTML5的特性
- 官方宣称的高达60fps的滚动刷新率以及内置手势
- Safari相同的JavaScript引擎
- 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议(官方文档说明)
- 另外用的比较多的,增加加载进度属性:
estimatedProgress
常用属性:
@property (nonatomic, readonly) BOOL canGoBack;
@property (nonatomic, readonly) BOOL canGoForward;
- (WKNavigation *)goBack;
- (WKNavigation *)goForward;
- (WKNavigation *)reload;
- (void)stopLoading;
/*
reloadFromOrigin会比较网络数据是否有变化,没有变化则使用缓存,否则从新请求。
goToBackForwardListItem:比向前向后更强大,可以跳转到某个指定历史页面
*/
- (WKNavigation *)reloadFromOrigin;
- (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
// 是否允许左右划手势导航,默认不允许
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
// 加载进度 0 ~ 1
@property (nonatomic, readonly) double estimatedProgress;
@property (nullable, nonatomic, readonly, copy) NSString *title;
// 访问历史列表,可以通过前进后退按钮访问,或者通过goToBackForwardListItem函数跳到指定页面
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
常用方法:
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL
#pragma mark - WKNavigationDelegate
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
}
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSLog(@"%@",navigationResponse.response.URL.absoluteString);
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"%@",navigationAction.request.URL.absoluteString);
//允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationActionPolicyCancel);
}
#pragma mark - WKUIDelegate
// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
return [[WKWebView alloc]init];
}
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
completionHandler(@"http");
}
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
completionHandler(YES);
}
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
NSLog(@"%@",message);
completionHandler();
}
网页加载进度监听:
// 注册观察者
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
// 监听网页加载进度
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
[self.progressView setProgress:self.webView.estimatedProgress animated:YES];
if (self.progressView.progress == 1) {
self.progressView.hidden = YES;
}
}
// 开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
self.progressView.hidden = NO;
}
// 加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
self.progressView.hidden = YES;
}
// 加载失败
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
self.progressView.hidden = YES;
}
js调动oc
首先需要添加WKScriptMessageHandler协议
// 为了避免循环引用,在viewWillAppear中添加js事件,在viewDidDisappear中移除
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// 配置js环境
WKUserContentController *userCC = self.webView.configuration.userContentController;
// 添加js事件
[userCC addScriptMessageHandler:self name:@"onClickName"];
}
// 处理js交互
// 前端必须使用window.webkit.messageHandlers.注册的方法名.postMessage({body:传输的数据}
/* 示例
window.webkit.messageHandlers.uploadRebate.postMessage({platName:data.platName,platId:data.platId,name:data.name,id:data.id})
前端给的方法名为uploadRebate,给的参数是一个字典
*/
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"拦截的方法名%@ , 拦截的方法传过来的参数%@", message.name, message.body);
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];{
// 移除js
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"onClickName"];
}
oc调用js
// 加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
//say()是JS方法名,completionHandler是异步回调block
[self.webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@",result);
}];
}
清除缓存
// 清除缓存
+(void)deleteWebCache{
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
/*
在磁盘缓存上。
WKWebsiteDataTypeDiskCache,
html离线Web应用程序缓存。
WKWebsiteDataTypeOfflineWebApplicationCache,
内存缓存。
WKWebsiteDataTypeMemoryCache,
本地存储。
WKWebsiteDataTypeLocalStorage,
Cookies
WKWebsiteDataTypeCookies,
会话存储
WKWebsiteDataTypeSessionStorage, IndexedDB数据库。
WKWebsiteDataTypeIndexedDBDatabases,
查询数据库。
WKWebsiteDataTypeWebSQLDatabases
*/
NSArray * types=@[WKWebsiteDataTypeCookies,WKWebsiteDataTypeLocalStorage];
NSSet *websiteDataTypes= [NSSet setWithArray:types];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
}];
} else {
// 清楚Library目录下的 Cookies文件夹与WebKit文件夹里面的内容
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
NSError *errors;
[[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors];
NSString *WebKit = [libraryPath stringByAppendingString:@"/WebKit"];
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:WebKit error:&error];
}
}
页面导航逐级返回
if ([webV canGoBack]) {
[webV goBack];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
监听网页链接的变化
有时候监听跳转的代理不走,不知道为什么,只能用kvo先解决一下了
[_webViews addObserver:self forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{
NSLog(@"url == %@",_webViews.URL.absoluteString);
}
小知识,wkwebview播放背景音乐
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.allowsInlineMediaPlayback = YES;
config.mediaPlaybackRequiresUserAction = false;
wkWebView=[[WKWebView alloc] initWithFrame:rect configuration:config];
wkWebView.UIDelegate=self;
wkWebView.navigationDelegate=self;
wkwebview加载HTML字符串
- (void)setWKWeb{
//js脚本 (脚本注入设置网页样式)
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
//注入
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];
//配置对象
WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
wkWebConfig.userContentController = wkUController;
//改变初始化方法 (这里一定要给个初始宽度,要不算的高度不对)
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, BKSCREEN_WIDTH - 30, 0) configuration:wkWebConfig];
webView.scrollView.bounces = NO;
_wkWeb = webView;
_wkWeb.navigationDelegate = self;
}
//网页加载完成
//页面加载完后获取高度,设置脚,注意,控制器里不能重写代理方法,否则这里会不执行
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
// document.body.scrollHeight(加载HTML源站用这个) document.body.offsetHeight;(加载HTML字符串)
[webView evaluateJavaScript:@"document.body.offsetHeight" completionHandler:^(id Result, NSError * error) {
NSString *heightStr = [NSString stringWithFormat:@"%@",Result];
//必须加上一点
CGFloat height = heightStr.floatValue+15.00;
//网页加载完成
NSLog(@"新闻加载完成网页高度:%f",height);
self.wkWebHeigh = height;
[self.tableV reloadData];
}];
}
补全网页代码,设置图片适应屏幕宽度(遍历网页中所有的图片,设置$img[p].style.width = '100%%')
NSString *htmlString = [NSString stringWithFormat:@"<html> \n"
"<head> \n"
"<style type=\"text/css\"> \n"
"body {font-size:15px;}\n"
"</style> \n"
"</head> \n"
"<body>"
"<script type='text/javascript'>"
"window.onload = function(){\n"
"var $img = document.getElementsByTagName('img');\n"
"for(var p in $img){\n"
" $img[p].style.width = '100%%';\n"
"$img[p].style.height ='auto'\n"
" let height = document.body.offsetHeight;\n" "window.webkit.messageHandlers.imagLoaded.postMessage(height);\n"
"}\n"
"}"
"</script>%@"
"</body>"
"</html>", self.baseModel.desc];
BKLog(@"HTML字符串:%@", htmlString);
[self.wkWeb loadHTMLString:htmlString baseURL:nil];
计算高度方法二:(在 HTML DOM 中 Event 有个函数 onload 是用于一张页面或一幅图像完成加载时所执行的,我们需要监听所有的 img
标签 或 body
标签,然后在这个方法里发个消息给 WebKit 然后进行拦截)
" let height = document.body.offsetHeight;\n" "window.webkit.messageHandlers.imagLoaded.postMessage(height);\n"
上面补全网页代码中这两行就是监听发送高度通知,然后我们只需要拦截imagLoaded这个js方法就可以拿到(document.body.offsetHeight)也就是网页的高度了
[self.wkWeb.configuration.userContentController addScriptMessageHandler:self name:@"imagLoaded"];
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"拦截的方法名%@ , 拦截的方法传过来的参数%@", message.name, message.body);
if ([message.name isEqualToString:@"imagLoaded"]) {
CGFloat height = [message.body floatValue];
self.wkWebHeigh = height;
[self.tableV reloadData];
}
}
计算高度方法三:监听网页的加载状态(loading),比监听"scrollView.contentSize"属性调用的少
[self.wkWeb addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"loading"]) {
[self.wkWeb evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id Result, NSError * error) {
NSString *heightStr = [NSString stringWithFormat:@"%@",Result];
//必须加上一点
CGFloat height = heightStr.floatValue;
if (height == 0) {
return ;
}
//网页加载完成
NSLog(@"新闻加载完成网页高度:%f",height);
self.wkWebHeigh = height;
[self.tableV reloadData];
}];
}
}