参考文章:http://www.cocoachina.com/ios/20180831/24753.html
WK时苹果在iOS8.0之后推出的控件,相比于UIWebView:
- 内存消耗少;
- 解决了网页加载时的内存泄漏问题;
- 与HTML页面的交互更方便;
- 总之,其性能比UIWebView好很多。
使用时,首先要添加头文件:
#import <WebKit/WebKit.h>
简单创建一个WKWebView:
self.iWKWebView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-64)]; //此处协议下面会讲到 self.iWKWebView.navigationDelegate = self; self.iWKWebView.UIDelegate = self; self.iWKWebView.allowsBackForwardNavigationGestures = YES; NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [self.iWKWebView loadRequest:request]; [self.view addSubview:self.iWKWebView];
基本用法和UIWebView差不多。
这里介绍几个主要的类:
- WKWebView
-
WKWebViewConfiguration
-
WKPreferences
-
WKUserContentController
-
WKWebsiteDataStore
1. WKWebView:
常用属性:
// 导航代理 @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate; // UI代理 @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate; // 页面标题, 一般使用KVO动态获取 @property (nullable, nonatomic, readonly, copy) NSString *title; // 页面加载进度, 一般使用KVO动态获取 @property (nonatomic, readonly) double estimatedProgress; // 可返回的页面列表, 已打开过的网页, 有点类似于navigationController的viewControllers属性 @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList; // 页面url @property (nullable, nonatomic, readonly, copy) NSURL *URL; // 页面是否在加载中 @property (nonatomic, readonly, getter=isLoading) BOOL loading; // 是否可返回 @property (nonatomic, readonly) BOOL canGoBack; // 是否可向前 @property (nonatomic, readonly) BOOL canGoForward; // WKWebView继承自UIView, 所以如果想设置scrollView的一些属性, 需要对此属性进行配置 @property (nonatomic, readonly, strong) UIScrollView *scrollView; // 是否允许手势左滑返回上一级, 类似导航控制的左滑返回 @property (nonatomic) BOOL allowsBackForwardNavigationGestures; //自定义UserAgent, 会覆盖默认的值 ,iOS 9之后有效 @property (nullable, nonatomic, copy) NSString *customUserAgent
常用方法:
// 带配置信息的初始化方法 // configuration 配置信息 - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration // 加载请求 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request; // 加载HTML - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; // 返回上一级 - (nullable WKNavigation *)goBack; // 前进下一级, 需要曾经打开过, 才能前进 - (nullable WKNavigation *)goForward; // 刷新页面 - (nullable WKNavigation *)reload; // 根据缓存有效期来刷新页面 - (nullable WKNavigation *)reloadFromOrigin; // 停止加载页面 - (void)stopLoading; // 执行JavaScript代码 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
2. WKWebViewConfiguration:配置信息
可以用配置信息来初始化WKWebView.
属性有:
//关于网页的设置 @property (nonatomic, strong) WKPreferences *preferences; //JavaScript与原生交互的桥梁 @property (nonatomic, strong) WKUserContentController *userContentController; //提供了网站所能使用的数据类型 @property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macosx(10.11), ios(9.0)); //是否允许播放媒体文件 @property (nonatomic) BOOL allowsAirPlayForMediaPlayback API_AVAILABLE(macosx(10.11), ios(9.0)); //是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放 @property (nonatomic) BOOL allowsInlineMediaPlayback; //需要用户允许才能播放的媒体类型 @property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback API_AVAILABLE(macosx(10.12), ios(10.0));
2.1 WKPreference:
WKPreferences *preference = [[WKPreferences alloc]init]; //最小字体大小 preference.minimumFontSize = 0; //是否支持javaScript,默认YES preference.javaScriptEnabled = YES; //是否允许不经过用户交互由javaScript自动打开窗口 preference.javaScriptCanOpenWindowsAutomatically = YES;
2.2 WKUserContentController
// 注入JavaScript与原生交互协议 // JS 端可通过 window.webkit.messageHandlers..postMessage() 发送消息 - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name; // 移除注入的协议, 在deinit方法中调用 - (void)removeScriptMessageHandlerForName:(NSString *)name; // 通过WKUserScript注入需要执行的JavaScript代码 - (void)addUserScript:(WKUserScript *)userScript; // 移除所有注入的JavaScript代码 - (void)removeAllUserScripts;
使用WKUserContentController注入的交互协议, 需要遵循WKScriptMessageHandler协议, 在其协议方法中获取JavaScript端传递的事件和参数:
JS调用OC:
简单理解就是:[userController addScriptMessageHandler:self name:@"JSSendToOC"];//userController是一个WKUserContentController对象,‘JSSendToOC’是方法名,
当JS端通过window.webkit.messageHandlers.JSSendToOC.postMessage()方法调用'JSSendToOC'方法时,我们可以通过下面的协议方法获取到JS端传过来的数据,做我们的操作。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; WKScriptMessage包含了传递的协议名称及参数, 主要从下面的属性中获取: // 协议名称, 即上面的add方法传递的name @property (nonatomic, readonly, copy) NSString *name; // 传递的参数 @property (nonatomic, readonly, copy) id body;
OC调用JS:
NSString *js = @"callJsFunction('hahaha')"; [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) { NSLog(@"response:%@..error:%@",response,error); }];
这里是调用了JS的‘callJsFunction’方法,这个方法名是随便起的。
2.3 WKWebsiteDataStore
WKWebsiteDataStore 提供了网站所能使用的数据类型,包括 cookies,硬盘缓存,内存缓存活在一些WebSQL的数据持久化和本地持久化。可通过 WKWebViewConfiguration类的属性 websiteDataStore 进行相关的设置。WKWebsiteDataStore相关的API也比较简单:
// 默认的data store + (WKWebsiteDataStore *)defaultDataStore; // 如果为webView设置了这个data Store,则不会有数据缓存被写入文件 // 当需要实现隐私浏览的时候,可使用这个 + (WKWebsiteDataStore *)nonPersistentDataStore; // 是否是可缓存数据的,只读 @property (nonatomic, readonly, getter=isPersistent) BOOL persistent; // 获取所有可使用的数据类型 + (NSSet<NSString *> *)allWebsiteDataTypes; // 查找指定类型的缓存数据 // 回调的值是WKWebsiteDataRecord的集合 - (void)fetchDataRecordsOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(NSArray<WKWebsiteDataRecord *> *))completionHandler; // 删除指定的纪录 // 这里的参数是通过上面的方法查找到的WKWebsiteDataRecord实例获取的 - (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler; // 删除某时间后修改的某类型的数据 - (void)removeDataOfTypes:(NSSet<NSString *> *)websiteDataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler; // 保存的HTTP cookies @property (nonatomic, readonly) WKHTTPCookieStore *httpCookieStore
dataTypes:
// 硬盘缓存 WKWebsiteDataTypeDiskCache, // HTML离线web应用程序缓存 WKWebsiteDataTypeOfflineWebApplicationCache, // 内存缓存 WKWebsiteDataTypeMemoryCache, // 本地缓存 WKWebsiteDataTypeLocalStorage, // cookies WKWebsiteDataTypeCookies, // HTML会话存储 WKWebsiteDataTypeSessionStorage, // IndexedDB 数据库 WKWebsiteDataTypeIndexedDBDatabases, // WebSQL 数据库 WKWebsiteDataTypeWebSQLDatabases
dataRecord:
// 展示名称, 通常是域名 @property (nonatomic, readonly, copy) NSString *displayName; // 包含的数据类型 @property (nonatomic, readonly, copy) NSSet<NSString *> *dataTypes;
关于此类的简单使用:
1.删除指定时间的所有类型数据
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes]; NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ // Done NSLog(@"释放"); }];
2.查找删除
WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * _Nonnull records) { for (WKWebsiteDataRecord *record in records) { [dataStore removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{ // done }]; } }];
3.查找删除特定的内容
WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * _Nonnull records) { for (WKWebsiteDataRecord *record in records) { if ([record.displayName isEqualToString:@"baidu"]) { [dataStore removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{ // done }]; } } }];
WKNavigationDelegate:
#pragma mark - WKNavigationDelegate //请求加载之前,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ NSLog(@"加载前允许跳转"); decisionHandler(WKNavigationActionPolicyAllow); } //开始加载时调用 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{ NSLog(@"开始加载"); } //收到响应开始加载后,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ NSLog(@"收到响应后允许跳转"); decisionHandler(WKNavigationResponsePolicyAllow); } //内容开始返回时调用 - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{ NSLog(@"开始返回内容"); } //加载完成时调用 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{ NSLog(@"加载完成"); self.title = webView.title; } //加载失败调用 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{ NSLog(@"加载失败"); } //收到服务器重定向请求后调用 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{ NSLog(@"服务器重定向"); } //当main frame最后下载数据失败时,会回调 - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{ NSLog(@"返回内容发生错误"); } //用于授权验证的API,与AFN、UIWebView的授权验证API是一样的 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,nil); } //当web content处理完成时,会回调 - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0)){ NSLog(@"WebContent完成"); }
这里放一个完整的WKWebView例子,仅供参考:
//初始化WKPreferences,并设置相关属性 WKPreferences *preference = [[WKPreferences alloc]init]; //初始化WKUserContentController,并设置相关属性 WKUserContentController *userController = [[WKUserContentController alloc]init]; // 添加在js中操作的对象名称,通过该对象来向web view发送消息 // JS 端可通过 window.webkit.messageHandlers..postMessage() 发送消息 // <script type="text/javascript"> // function clickBtn(){ // var dict = {"name":"tom","age":"20"}; // window.webkit.messageHandlers.JSSendToOC.postMessage(dict); // } // </script> [userController addScriptMessageHandler:self name:@"JSSendToOC"]; //初始化WKWebsiteDataStore,并设置相关属性 WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; // 如果为webView设置了这个data Store,则不会有数据缓存被写入文件 // 当需要实现隐私浏览的时候,可使用这个 // WKWebsiteDataStore *dataStore = [WKWebsiteDataStore nonPersistentDataStore]; //配置信息 WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init]; configuration.preferences = preference; configuration.userContentController = userController; configuration.websiteDataStore = dataStore; self.iWKWebView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-64) configuration:configuration]; self.iWKWebView.navigationDelegate = self; self.iWKWebView.UIDelegate = self; self.iWKWebView.allowsBackForwardNavigationGestures = YES; NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [self.iWKWebView loadRequest:request]; [self.view addSubview:self.iWKWebView];
再加一个知识点:WKWebView加载的时候添加一个自定义的进度条。
此时我们需要获取到webview加载的进度数值。
这里可以通过添加监听来获取。
[self.iWKWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
estimatedProgress是WKWebView的一个属性。
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"estimatedProgress"] && object==self.iWKWebView) { //获取到webview的进度数值,加载自定义的进度条 //self.iWKWebView.estimatedProgress } }