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];
}];
}
}
内存泄漏解决:
@interface WeakScriptMessageDelegate : NSObject
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end
注入方法改写:
[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:scriptMessage];
dealloc中移除:
[self.config.userContentController removeScriptMessageHandlerForName:scriptMessage];
图片自适应屏幕宽度:
// 在网页加载完成的代理中添加如下js就行了 maxwidth可以自己设置
NSString *js = @"function imgAutoFit() { \
var maxwidth = %f;\
var imgs = document.getElementsByTagName('img'); \
for (var i = 0; i < imgs.length; ++i) {\
var img = imgs[i]; \
if(img.width > maxwidth){ \
img.width = maxwidth; \
}\
} \
}";
js = [NSString stringWithFormat:js, self.meetingContentWebView.bounds.size.width];
[webView evaluateJavaScript:js completionHandler:nil];
采坑:
1、IOS11一下,wkwebview加载网页只显示一半。。。。(tmd,IOS11一下系统渲染问题,wkwebview不太兼容)
滑动时不停的刷新布局就行了
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.tableV) {
[self.wkWeb setNeedsLayout];
}
}
来源:oschina
链接:https://my.oschina.net/u/2862829/blog/1828053