前言
最近項目中的UIWebView被替換為了WKWebView,因此來總結(jié)一下耳峦。
示例Demo:WKWebView的使用
本文將從以下幾方面介紹WKWebView:
- 1蹲坷、WKWebView涉及的一些類
- 2循签、WKWebView涉及的代理方法
- 3县匠、網(wǎng)頁內(nèi)容加載進(jìn)度條和title的實現(xiàn)
- 4乞旦、JS和OC的交互
- 5兰粉、本地HTML文件的實現(xiàn)
- 6玖姑、WKWebView+UITableView混排
- 7客峭、WKWebView離線緩存功能
一舔琅、WKWebView涉及的一些類
- WKWebView:網(wǎng)頁的渲染與展示
注意: #import <WebKit/WebKit.h>
//初始化
_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) configuration:config];
// UI代理
_webView.UIDelegate = self;
// 導(dǎo)航代理
_webView.navigationDelegate = self;
// 是否允許手勢左滑返回上一級, 類似導(dǎo)航控制的左滑返回
_webView.allowsBackForwardNavigationGestures = YES;
//可返回的頁面列表, 存儲已打開過的網(wǎng)頁
WKBackForwardList * backForwardList = [_webView backForwardList];
// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.chinadaily.com.cn"]];
// [request addValue:[self readCurrentCookieWithDomain:@"http://www.chinadaily.com.cn"] forHTTPHeaderField:@"Cookie"];
// [_webView loadRequest:request];
//頁面后退
[_webView goBack];
//頁面前進(jìn)
[_webView goForward];
//刷新當(dāng)前頁面
[_webView reload];
NSString *path = [[NSBundle mainBundle] pathForResource:@"JStoOC.html" ofType:nil];
NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//加載本地html文件
[_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
- WKWebViewConfiguration:為添加WKWebView配置信息
//創(chuàng)建網(wǎng)頁配置對象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 創(chuàng)建設(shè)置對象
WKPreferences *preference = [[WKPreferences alloc]init];
//最小字體大小 當(dāng)將javaScriptEnabled屬性設(shè)置為NO時课蔬,可以看到明顯的效果
preference.minimumFontSize = 0;
//設(shè)置是否支持javaScript 默認(rèn)是支持的
preference.javaScriptEnabled = YES;
// 在iOS上默認(rèn)為NO,表示是否允許不經(jīng)過用戶交互由javaScript自動打開窗口
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;
// 是使用h5的視頻播放器在線播放, 還是使用原生播放器全屏播放
config.allowsInlineMediaPlayback = YES;
//設(shè)置視頻是否需要用戶手動播放 設(shè)置為NO則會允許自動播放
config.requiresUserActionForMediaPlayback = YES;
//設(shè)置是否允許畫中畫技術(shù) 在特定設(shè)備上有效
config.allowsPictureInPictureMediaPlayback = YES;
//設(shè)置請求的User-Agent信息中應(yīng)用程序名稱 iOS9后可用
config.applicationNameForUserAgent = @"ChinaDailyForiPad";
//自定義的WKScriptMessageHandler 是為了解決內(nèi)存不釋放的問題
WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
//這個類主要用來做native與JavaScript的交互管理
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//注冊一個name為jsToOcNoPrams的js方法
[wkUController addScriptMessageHandler:weakScriptMessageDelegate name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:weakScriptMessageDelegate name:@"jsToOcWithPrams"];
config.userContentController = wkUController;
- WKUserScript:用于進(jìn)行JavaScript注入
//以下代碼適配文本大小,由UIWebView換為WKWebView后谚鄙,會發(fā)現(xiàn)字體小了很多闷营,這應(yīng)該是WKWebView與html的兼容問題知市,解決辦法是修改原網(wǎng)頁嫂丙,要么我們手動注入JS
NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
//用于進(jìn)行JavaScript注入
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:wkUScript];
- WKUserContentController:這個類主要用來做native與JavaScript的交互管理
//這個類主要用來做native與JavaScript的交互管理
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//注冊一個name為jsToOcNoPrams的js方法跟啤,設(shè)置處理接收J(rèn)S方法的代理
[wkUController addScriptMessageHandler:self name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:self name:@"jsToOcWithPrams"];
config.userContentController = wkUController;
//用完記得移除
//移除注冊的js方法
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcNoPrams"];
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcWithPrams"];
- WKScriptMessageHandler:這個協(xié)議類專門用來處理監(jiān)聽JavaScript方法從而調(diào)用原生OC方法,和WKUserContentController搭配使用糊秆。
注意:遵守WKScriptMessageHandler協(xié)議痘番,代理是由WKUserContentControl設(shè)置
//通過接收J(rèn)S傳出消息的name進(jìn)行捕捉的回調(diào)方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
//用message.body獲得JS傳出的參數(shù)體
NSDictionary * parameter = message.body;
//JS調(diào)用OC
if([message.name isEqualToString:@"jsToOcNoPrams"]){
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"js調(diào)用到了oc" message:@"不帶參數(shù)" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}])];
[self presentViewController:alertController animated:YES completion:nil];
}else if([message.name isEqualToString:@"jsToOcWithPrams"]){
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"js調(diào)用到了oc" message:parameter[@"params"] preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
}
二、WKWebView涉及的代理方法
- WKNavigationDelegate :主要處理一些跳轉(zhuǎn)昂芜、加載處理操作
// 頁面開始加載時調(diào)用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
}
// 頁面加載失敗時調(diào)用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
[self.progressView setProgress:0.0f animated:NO];
}
// 當(dāng)內(nèi)容開始返回時調(diào)用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
}
// 頁面加載完成之后調(diào)用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[self getCookie];
}
//提交發(fā)生錯誤時調(diào)用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self.progressView setProgress:0.0f animated:NO];
}
// 接收到服務(wù)器跳轉(zhuǎn)請求即服務(wù)重定向時之后調(diào)用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
}
// 根據(jù)WebView對于即將跳轉(zhuǎn)的HTTP請求頭信息和相關(guān)信息來決定是否跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString * urlStr = navigationAction.request.URL.absoluteString;
NSLog(@"發(fā)送跳轉(zhuǎn)請求:%@",urlStr);
//自己定義的協(xié)議頭
NSString *htmlHeadString = @"github://";
if([urlStr hasPrefix:htmlHeadString]){
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"通過截取URL調(diào)用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);
}
}
// 根據(jù)客戶端受到的服務(wù)器響應(yīng)頭以及response相關(guān)信息來決定是否可以跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSString * urlStr = navigationResponse.response.URL.absoluteString;
NSLog(@"當(dāng)前跳轉(zhuǎn)地址:%@",urlStr);
//允許跳轉(zhuǎn)
decisionHandler(WKNavigationResponsePolicyAllow);
//不允許跳轉(zhuǎn)
//decisionHandler(WKNavigationResponsePolicyCancel);
}
//需要響應(yīng)身份驗證時調(diào)用 同樣在block中需要傳入用戶身份憑證
- (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 的發(fā)送方提供 credential
[challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
//進(jìn)程被終止時調(diào)用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
}
- WKUIDelegate :主要處理JS腳本欢际,確認(rèn)框矾兜,警告框等
/**
* web界面中有彈出警告框時調(diào)用
*
* @param webView 實現(xiàn)該代理的webview
* @param message 警告框中的內(nèi)容
* @param completionHandler 警告框消失調(diào)用
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"HTML的彈出框" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// 確認(rèn)框
//JavaScript調(diào)用confirm方法后回調(diào)的方法 confirm是js中的確定框损趋,需要在block中把用戶選擇的情況傳遞進(jìn)去
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
// 輸入框
//JavaScript調(diào)用prompt方法后回調(diào)的方法 prompt是js中的輸入框 需要在block中把用戶輸入的信息傳入
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self presentViewController:alertController 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;
}
三、網(wǎng)頁內(nèi)容加載進(jìn)度條和title的實現(xiàn)
//添加監(jiān)測網(wǎng)頁加載進(jìn)度的觀察者
[self.webView addObserver:self
forKeyPath:@"estimatedProgress"
options:0
context:nil];
//添加監(jiān)測網(wǎng)頁標(biāo)題title的觀察者
[self.webView addObserver:self
forKeyPath:@"title"
options:NSKeyValueObservingOptionNew
context:nil];
//kvo 監(jiān)聽進(jìn)度 必須實現(xiàn)此方法
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))]
&& object == _webView) {
NSLog(@"網(wǎng)頁加載進(jìn)度 = %f",_webView.estimatedProgress);
self.progressView.progress = _webView.estimatedProgress;
if (_webView.estimatedProgress >= 1.0f) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.progressView.progress = 0;
});
}
}else if([keyPath isEqualToString:@"title"]
&& object == _webView){
self.navigationItem.title = _webView.title;
}else{
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
//移除觀察者
[_webView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[_webView removeObserver:self
forKeyPath:NSStringFromSelector(@selector(title))];
四椅寺、JS和OC的交互
- JS調(diào)用OC
這個實現(xiàn)主要是依靠WKScriptMessageHandler協(xié)議類和WKUserContentController兩個類:WKUserContentController對象負(fù)責(zé)注冊JS方法浑槽,設(shè)置處理接收J(rèn)S方法的代理,代理遵守WKScriptMessageHandler返帕,實現(xiàn)捕捉到JS消息的回調(diào)方法,詳情可以看第一步中對這兩個類的介紹溉旋。
//這個類主要用來做native與JavaScript的交互管理
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//注冊一個name為jsToOcNoPrams的js方法,設(shè)置處理接收J(rèn)S方法的代理
[wkUController addScriptMessageHandler:self name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:self name:@"jsToOcWithPrams"];
config.userContentController = wkUController;
注意:遵守WKScriptMessageHandler協(xié)議嫉髓,代理是由WKUserContentControl設(shè)置
//通過接收J(rèn)S傳出消息的name進(jìn)行捕捉的回調(diào)方法 js調(diào)OC
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
}
- OC調(diào)用JS
//OC調(diào)用JS changeColor()是JS方法名观腊,completionHandler是異步回調(diào)block
NSString *jsString = [NSString stringWithFormat:@"changeColor('%@')", @"Js參數(shù)"];
[_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) {
NSLog(@"改變HTML的背景色");
}];
//改變字體大小 調(diào)用原生JS方法
NSString *jsFont = [NSString stringWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'", arc4random()%99 + 100];
[_webView evaluateJavaScript:jsFont completionHandler:nil];
五、本地HTML文件的實現(xiàn)
由于示例Demo的需要以及知識有限算行,我用僅知的HTML梧油、CSS、JavaScript的一點皮毛寫了一個HTML文件州邢,比較業(yè)余儡陨,大神勿噴????
小白想學(xué)習(xí)這方面的知識可以看這里: http://www.w3school.com.cn/index.html
我用MAC自帶的文本編輯工具褪子,生成一個文件,改后綴名骗村,強轉(zhuǎn)為.html文件嫌褪,同時還需要設(shè)置文本編輯打開HTML文件時顯示代碼(如下圖),然后編輯代碼胚股。
6笼痛、WKWebView+UITableView混排 和 7、WKWebView離線緩存功能 相關(guān)內(nèi)容在 iOS_Tips-12 這里查看琅拌。
詳情請前往我的Github:WKWebView的使用
如果我WKWebView使用的總結(jié)沒幫到你缨伊,你也可以看看下面幾篇文:
http://www.reibang.com/p/833448c30d70
http://www.reibang.com/p/4fa8c4eb1316
http://www.reibang.com/p/91cfe58c032d
WKWebView的那些坑
如果需要跟我交流的話:
※ Github: https://github.com/wsl2ls
※ 掘金:https://juejin.im/user/5c00d97b6fb9a049fb436288
※ 簡書:http://www.reibang.com/u/e15d1f644bea