WKWebView介绍
主要类:
- WKWebView
- WKWebViewConfiguration:
- WKUserScript:
- WKUserContentController:
- WKWebsiteDataStore:
主要代理:
- WKNavigationDelegate
- WKUIDelegate
1. WKWebView
1.1 常用属性
// 导航代理
@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
1.2 常用方法
// 带配置信息的初始化方法
// 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
网页配置模型,仅在初始化时可设置,初始化后无法修改。
/*webview的进程池,与页面的资源存储有关
Tips:前端使用 localstorage 进行存储,A页面和B页面都存在 一个WKWebView,在B页面使用localstorage保存信息,回到A页面取不到最新的数据。
解决方案:将processPool设置成单例。
*/
@property (nonatomic, strong) WKProcessPool *processPool;
// webview 偏好设置,可设置最小字体等属性
@property (nonatomic, strong) WKPreferences *preferences;
// 专门用来管理native与JavaScript的交互行为
@property (nonatomic, strong) WKUserContentController *userContentController;
// web持久化,包括 cookie、磁盘和内存缓存以及持久性数据,例如 WebSQL、IndexedDB 数据库和本地存储
@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macos(10.11), ios(9.0));
// 是否在所有的数据加载到内存中才开始渲染,默认为 NO。加载网页时,网页会逐渐呈现,设置为true,仅在加载所有内容后才显示网页。
@property (nonatomic) BOOL suppressesIncrementalRendering;
// 设置User-Agent最后的名字,用于标记应用。
@property (nullable, nonatomic, copy) NSString *applicationNameForUserAgent API_AVAILABLE(macos(10.11), ios(9.0));
// 是否允许播放媒体文件,默认为YES
@property (nonatomic) BOOL allowsAirPlayForMediaPlayback API_AVAILABLE(macos(10.11), ios(9.0));
// 指对已知支持 HTTPS 的服务器的 HTTP 请求是否应自动升级为 HTTPS 请求。默认为YES
@property (nonatomic) BOOL upgradeKnownHostsToHTTPS API_AVAILABLE(macos(11.3), ios(14.5));
// 网页中的多媒体是否需要手势才能开始播放(iOS 10) 可以设置仅音频需要、仅视频需要等四种状态
@property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback API_AVAILABLE(macos(10.12), ios(10.0));
// 决定了用内嵌HTML5播放视频还是用本地的全屏控制。默认为NO
@property (nonatomic) BOOL allowsInlineMediaPlayback;
// 控制用户与webview进行选择交互时的粒度,可以选择整个块儿,或单个符号.
@property (nonatomic) WKSelectionGranularity selectionGranularity;
3. WKUserContentController
WKUserContentController 是JavaScript与原生进行交互的桥梁,主要方法:
// JS 端可通过 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 发送消息
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
// 移除注入的协议, 在deinit方法中调用
- (void)removeScriptMessageHandlerForName:(NSString *)name;
// 通过WKUserScript注入需要执行的JavaScript代码
- (void)addUserScript:(WKUserScript *)userScript;
// 移除所有注入的JavaScript代码
- (void)removeAllUserScripts;
可通过
addScriptMessageHandler:name:
方法注册JS与Native交互名字,可通过addUserScript :
注入需要执行的JS代码。
3.1 JavaScript调用Native
Native代码:
#pragma mark - Native配置代码
//创建网页配置对象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
//自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
//这个类主要用来做native与JavaScript的交互管理
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//注册一个name为jsToOcNoPrams的js方法 设置处理接收JS方法的对象
[wkUController addScriptMessageHandler:weakScriptMessageDelegate name:@"jsToNative"];
config.userContentController = wkUController;
#pragma mark - Native代理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
//用message.body获得JS传出的参数体
NSDictionary * parameter = message.body;
//JS调用OC
if([message.name isEqualToString:@"jsToNative"]){
// do something
}
}
JavaScript代码:
window.webkit.messageHandlers.jsToNative.postMessage({"params":"我是参数"});
4. WKUserScript
在WebKit框架中,我们还可以预先添加JS方法,供其他人员调用。WKUserScript就是帮助我们完成JS注入的类,它能帮助我们在页面填充前或js填充完成后调用。
//以下代码适配文本大小
NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
// NSString *jSString = @"alert(\"WKUserScript注入js\");";
//用于进行JavaScript注入
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:wkUScript];
5. WKUIDelegate
这个代理方法, 主要是用来处理使用系统的弹框来替换JS中的一些弹框的,比如: 警告框, 选择框, 输入框, 主要使用的是下面三个代理方法:
/** 对应js的alert方法
webView中弹出警告框时调用, 只能有一个按钮
@param webView webView
@param message 提示信息
@param frame 可用于区分哪个窗口调用的
@param completionHandler 警告框消失的时候调用, 回调给JS
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"警告" message:message preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"我知道了" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
/** 对应js的confirm方法
webView中弹出选择框时调用, 两个按钮
@param webView webView description
@param message 提示信息
@param frame 可用于区分哪个窗口调用的
@param completionHandler 确认框消失的时候调用, 回调给JS, 参数为选择结果: YES or NO
*/
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请选择" message:message preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"同意" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"不同意" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}];
[alert addAction:ok];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
/** 对应js的prompt方法
webView中弹出输入框时调用, 两个按钮 和 一个输入框
@param webView webView description
@param prompt 提示信息
@param defaultText 默认提示文本
@param frame 可用于区分哪个窗口调用的
@param completionHandler 输入框消失的时候调用, 回调给JS, 参数为输入的内容
*/
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"请输入" message:prompt preferredStyle:(UIAlertControllerStyleAlert)];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"请输入";
}];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
UITextField *tf = [alert.textFields firstObject];
completionHandler(tf.text);
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:^(UIAlertAction * _Nonnull action) {
completionHandler(defaultText);
}];
[alert addAction:ok];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
// 页面是弹出窗口 _blank 处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
6. WKNavigationDelegate
主要处理一些跳转、加载处理操作。
// main frame的导航开始请求时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
NSLog(@"开始加载");
}
// 当main frame开始加载数据失败时,会回调
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
[self.progressView setProgress:0.0f animated:NO];
}
// 当内容开始返回时调用
- (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 {
[self.progressView setProgress:0.0f animated:NO];
}
// 当main frame接收到服务重定向时调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
// 接收到服务器跳转请求之后调用
}
// 根据WebView对于即将跳转的HTTP请求头信息和相关信息来决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString * urlStr = navigationAction.request.URL.absoluteString;
NSLog(@"发送跳转请求:%@",urlStr);
//自己定义的协议头
NSString *htmlHeadString = @"github://";
if([urlStr hasPrefix:htmlHeadString]){
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"通过截取URL调用OC" message:@"你想前往我的Github主页?" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"打开" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSURL * url = [NSURL URLWithString:[urlStr stringByReplacingOccurrencesOfString:@"github://callName_?" withString:@""]];
[[UIApplication sharedApplication] openURL:url];
}])];
[self presentViewController:alertController animated:YES completion:nil];
decisionHandler(WKNavigationActionPolicyCancel);
}else{
decisionHandler(WKNavigationActionPolicyAllow);
}
}
// 根据客户端受到的服务器响应头以及response相关信息来决定是否可以跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSString * urlStr = navigationResponse.response.URL.absoluteString;
NSLog(@"当前跳转地址:%@",urlStr);
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
//用于授权验证的API,与AFN、UIWebView的授权验证API是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler{
//用户身份信息
NSURLCredential * newCred = [[NSURLCredential alloc] initWithUser:@"user123" password:@"123" persistence:NSURLCredentialPersistenceNone];
//为 challenge 的发送方提供 credential
[challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
// 当web content处理完成时,会回调
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
}
7. WKWebsiteDataStore
WKWebsiteDataStore 提供了网站所能使用的数据类型,包括 cookies,硬盘缓存,内存缓存活在一些WebSQL的数据持久化和本地持久化。可通过 WKWebViewConfiguration 类的属性 websiteDataStore 进行相关的设置。
// 默认的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
7.1 WebsiteDataTypes
// 硬盘缓存
WKWebsiteDataTypeDiskCache,
// HTML离线web应用程序缓存
WKWebsiteDataTypeOfflineWebApplicationCache,
// 内存缓存
WKWebsiteDataTypeMemoryCache,
// 本地缓存
WKWebsiteDataTypeLocalStorage,
// cookies
WKWebsiteDataTypeCookies,
// HTML会话存储
WKWebsiteDataTypeSessionStorage,
// IndexedDB 数据库
WKWebsiteDataTypeIndexedDBDatabases,
// WebSQL 数据库
WKWebsiteDataTypeWebSQLDatabases
7.2 WKHTTPCookieStore
用于管理WKWebView中的Cookie
/*! 查找所有已存储的cookie
*/
- (void)getAllCookies:(void (^)(NSArray<NSHTTPCookie *> *))completionHandler;
/*! 保存一个cookie, 保存成功后, 会走一次回调方法
*/
- (void)setCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler;
/*! 删除一个cookie, 待删除的cookie对象可通过 'getAllCookies' 方法获取
*/
- (void)deleteCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler;
/*! 添加一个观察者, 需要遵循协议 WKHTTPCookieStoreObserver
当cookie发送变化时, 会通过 WKHTTPCookieStoreObserver 的协议方法通知该观察者, 在使用完后需要移除观察者
*/
- (void)addObserver:(id<WKHTTPCookieStoreObserver>)observer;
/*! 移除观察者
*/
- (void)removeObserver:(id<WKHTTPCookieStoreObserver>)observer;