前言
最近項目中UIWebView被替換成WKWebView骏啰,因此來總結(jié)一下。
本文將從以下幾方面介紹WKWebView。
- 1睦疫、WKWebView的創(chuàng)建
- 2、WKWebView的代理方法
- 3鞭呕、Html進度進度條的展示和title的實時獲取
- 4蛤育、JS和OC交互
一、WKWebView的創(chuàng)建
- WKWebView 創(chuàng)建主要涉及的類
WKUserScript:主要是用于JS的注入
WKPreferences:主要是設(shè)置WKWebview的屬性
WKWebPagePreferences:iOS13后推出設(shè)置是否支持JavaScript
WKWebViewConfiguration:為WKWebView添加配置信息
WKUserContentController:主要是管理JS與Native交互
- WKWebView 初始化
注意: #import <WebKit/WebKit.h>
//初始化
_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREENH_HEIGHT) configuration:config];
//UI 代理
_webView.UIDelegate = self;
//navigation delegate
_webView.navigationDelegate = self;
//是否允許手勢左滑返回上一級, 默認為NO
_webView.allowsBackForwardNavigationGestures = YES;
//可返回的頁面列表, 存儲已打開過的網(wǎng)頁
WKBackForwardList *backList = [_webView backForwardList];
NSLog(@"可返回頁面列表:%@",backList);
//加載URL
// NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
// NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
// [_webView loadRequest:request];
//加載本地HTML
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"JStoOC" ofType:@"html"];
NSString *htmlString = [[NSString alloc] initWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
[_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
- WKWebViewConfiguration 為WKWebView添加配置信息
//創(chuàng)建網(wǎng)頁配置對象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
//創(chuàng)建設(shè)置對象
WKPreferences *preferences = [[WKPreferences alloc] init];
//最小字體大小 當將javaScriptEnable
preferences.minimumFontSize = 0;
//設(shè)置是否支持javascript,默認是YES葫松。
if (@available(iOS 14.0, *)) {
//iOS13后新增的類,allowsContentJavaScript是iOS14后新增的方法瓦糕。
WKWebpagePreferences *webpagePreferences = [[WKWebpagePreferences alloc] init];
webpagePreferences.allowsContentJavaScript = YES;
config.defaultWebpagePreferences = webpagePreferences;
}else{
preferences.javaScriptEnabled = YES;
}
//是否允許不經(jīng)過用戶交互由JavaScript自動打開窗口,默認為NO
preferences.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preferences;
//YES是使用h5的視頻播放器在線播放腋么,NO是使用原生播放器全屏播放咕娄,默認為NO
config.allowsInlineMediaPlayback = YES;
//設(shè)置視頻是否需要手動播放,設(shè)置為NO會自動播放党晋。
config.requiresUserActionForMediaPlayback = YES;
//設(shè)置是否允許畫中畫播放谭胚,默認為YES
config.allowsPictureInPictureMediaPlayback = YES;
//設(shè)置請求的User-Agent信息中的應(yīng)用程序名稱 iOS9后可用
config.applicationNameForUserAgent = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
- WKUserScript :用于進行JavaScript注入
//適配字體大小
NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
//用于JS注入
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:userScript];
- WKUserContentController:用于管理native和JavaScript交互
//解決WKWebView內(nèi)存不釋放問題
WeakScriptMessageHandler *weakScriptMessageHandler = [[WeakScriptMessageHandler alloc] initWithDelegate:self];
//主要用來管理native與JavaScript的交互管理
WKUserContentController * userContentC = [[WKUserContentController alloc] init];
//注冊name為JStoOCNoParams的js方法,設(shè)置處理接收JS方法的對象self
[userContentC addScriptMessageHandler:weakScriptMessageHandler name:@"JStoOCNoParams"];
//注冊name為JStoOCWithParams的js方法未玻,設(shè)置處理接收JS方法的對象self
[userContentC addScriptMessageHandler:weakScriptMessageHandler name:@"JStoOCWithParams"];
config.userContentController = userContentC;
- WKScriptMessageHandler:該協(xié)議專門用來監(jiān)聽JavaScript調(diào)用OC方法灾而。與WKUserContentController搭配使用
#pragma mark - 處理JS調(diào)用Native方法的代理方法。通過message.name來區(qū)分扳剿。
//注意:遵守WKScriptMessageHandler協(xié)議旁趟,代理由WKUserContentController設(shè)置
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSString *actionName = message.name;
NSDictionary *params = message.body;
if (actionName.length >0) {
if ([actionName isEqualToString:@"JStoOCNoParams"]) {
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"提示" message:@"無參數(shù)" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil];
[alertC addAction:action];
[self presentViewController:alertC animated:YES completion:nil];
}else if ([actionName isEqualToString:@"JStoOCWithParams"]){
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"提示" message:params[@"params"] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil];
[alertC addAction:action];
[self presentViewController:alertC animated:YES completion:nil];
}
}
}
- 注意在dealloc方法中進行移除注冊的JS方法
-(void)dealloc
{
//移除注冊的JS方法
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"JStoOCNoParams"];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"JStoOCWithParams"];
}
二、WKWebView的代理方法
UIDelegate:主要處理JS腳本庇绽、確認框锡搜、警示框等
#pragma mark - UI Delegate
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
}
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
{
}
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler
{
}
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
return nil;
}
WKNavigationDelegate:主要處理一些跳轉(zhuǎn)、加載處理操作
#pragma mark - NavigationDelegate
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
//加載結(jié)束
}
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
//加載內(nèi)容開始返回時
}
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
//開始加載
}
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
//加載時發(fā)生錯誤
[self.progressView setProgress:0.0 animated:YES];
}
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
{
//接收到服務(wù)器跳轉(zhuǎn)請求即服務(wù)重定向時調(diào)用
}
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
//提交時發(fā)生錯誤
[self.progressView setProgress:0.0 animated:YES];
}
//根據(jù)webview對于即將跳轉(zhuǎn)的HTTP請求頭信息和相關(guān)信息來決定是否跳轉(zhuǎn)
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSString *urlString = navigationAction.request.URL.absoluteString;
NSString *htmlHeadString = @"github://callName_";
if ([urlString hasPrefix:htmlHeadString]) {
//進行客戶端代碼
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"提示" message:@"是否跳轉(zhuǎn)到該頁面瞧掺?" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"打開" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
dispatch_async(dispatch_get_main_queue(), ^{
//Safari打開url
NSURL *url = [NSURL URLWithString:[urlString stringByReplacingOccurrencesOfString:@"github://callName_?" withString:@""]];
[[UIApplication sharedApplication] openURL:url];
});
}];
[alertC addAction:cancelAction];
[alertC addAction:okAction];
[self presentViewController:alertC 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 *urlString = navigationResponse.response.URL.absoluteString;
NSLog(@"%@",urlString);
//根據(jù)URL來進行攔截或者阻止跳轉(zhuǎn)
decisionHandler(WKNavigationResponsePolicyAllow);//允許跳轉(zhuǎn)
// decisionHandler(WKNavigationResponsePolicyCancel);//不允許跳轉(zhuǎn)
}
//需要相應(yīng)身份驗證是調(diào)用 在block中需要傳入用戶身份憑證
-(void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
//用戶身份信息
NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:@"User" password:@"Password" persistence:NSURLCredentialPersistenceNone];
//為challenge的發(fā)送方提供credential
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
三耕餐、進度條和title
- 注冊觀察者
//添加監(jiān)測網(wǎng)頁title變化的觀察者(self) 被觀察者(self.webView)
[self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
//添加監(jiān)測網(wǎng)頁加載進度變化的觀察者
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
- 監(jiān)聽回調(diào)方法
#pragma mark - 觀察者的監(jiān)聽方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"title"] && object == _webView) {
self.title = change[@"new"];
}else if ([keyPath isEqualToString:@"estimatedProgress"] && object == _webView){
self.progressView.progress = _webView.estimatedProgress;
if (_webView.estimatedProgress >= 1.0f) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.progressView.progress = 0.0f;
});
}
}
}
- 注意 dealloc中移除觀察者
//移除觀察者
[self.webView removeObserver:self forKeyPath:@"title"];
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
四、OC與JS交互
- OC調(diào)用JS方法
#pragma mark - Native調(diào)用JS方法
-(void)OCtoJS
{
//action:changeColor 更換背景顏色
[_webView evaluateJavaScript:[[NSString alloc] initWithFormat:@"changeColor('')"] completionHandler:nil];
//id:pictureId action:changePicture path:圖片路徑 根據(jù)id更換圖片
NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"girl.png" ofType:nil];
NSString *jsString = [[NSString alloc] initWithFormat:@"changePicture('pictureId','%@')",imgPath];
[_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) {
NSLog(@"完成更換圖片");
}];
//改變字體大小
NSString *jsFont = [NSString stringWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'", arc4random()%99 + 20];
[_webView evaluateJavaScript:jsFont completionHandler:nil];
}
- JS調(diào)用OC方法
WKUserContentController * userContentC = [[WKUserContentController alloc] init];
//注冊name為JStoOCNoParams的js方法辟狈,設(shè)置處理接收JS方法的對象self
[userContentC addScriptMessageHandler:weakScriptMessageHandler name:@"JStoOCNoParams"];
//注冊name為JStoOCWithParams的js方法肠缔,設(shè)置處理接收JS方法的對象self
[userContentC addScriptMessageHandler:weakScriptMessageHandler name:@"JStoOCWithParams"];
config.userContentController = userContentC;
注意:遵守WKScriptMessageHandler協(xié)議夏跷,代理是由WKUserContentControl設(shè)置
//通過接收JS傳出消息的name進行捕捉的回調(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);
}