WKWebView詳解

1. 概述

從iOS8開始,就引入了新的瀏覽器控件WKWebView览妖,用于取代UIWebView,但是由于UIWebView的簡(jiǎn)單易用揽祥,還是使用率很高讽膏,目前蘋果已經(jīng)在迭代時(shí),會(huì)發(fā)警告??提醒更換控件盔然,新應(yīng)用必須使用WKWebView桅打,到了告別UIWebView的時(shí)候了....

那么WKWebView究竟好在哪里呢是嗜?

  1. 內(nèi)存開銷更小
  2. 內(nèi)置手勢(shì)
  3. 支持更多H5特性
  4. 有Safari相同的JavaScript引擎
  5. 提供更多屬性愈案,比如加載進(jìn)度、標(biāo)題鹅搪、準(zhǔn)確的得到頁(yè)面數(shù)等等
  6. 提供了更精細(xì)的加載流程回調(diào)(當(dāng)然相比UIWebView看起來也更麻煩一些站绪,畢竟方法多了)

1.1 UIWebView和WKWebView的流程對(duì)比

WKWebView的流程粒度更加細(xì)致,不但在請(qǐng)求的時(shí)候會(huì)詢問WKWebView是否請(qǐng)求數(shù)據(jù)丽柿,還會(huì)在返回?cái)?shù)據(jù)之后詢問WKWebView是否加載數(shù)據(jù)

我曾經(jīng)有個(gè)需求恢准,點(diǎn)擊鏈接的時(shí)候,如果是圖片那就下載而不是跳轉(zhuǎn)甫题,用UIWebView就不好做馁筐,因?yàn)槟悴恢梨溄訉?duì)應(yīng)的到底是什么文件(有重定向),如果用WKWebView坠非,我就可以在數(shù)據(jù)返回的時(shí)候判斷MIMEType做出不同的跳轉(zhuǎn)策略

左邊是UIWebView敏沉,右邊是WKWebView

2. WKWebView的基本使用

2.1 引入WKWebView

  1. 頭文件引入#import <WebKit/WebKit.h>
  2. 在targets中添加WebKit.framework庫(kù)


    WebKit.framework

2.2 WKWebView初始化

可以在初始化的時(shí)候,加入一些配置選項(xiàng)

- (WKWebView *)webView
{
    if (nil == _webView) {
        // 可以做一些初始化配置定制
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
        configuration.selectionGranularity = WKSelectionGranularityDynamic;
        configuration.allowsInlineMediaPlayback = YES;
        
        WKPreferences *preferences = [WKPreferences new];
        //是否支持JavaScript
        preferences.javaScriptEnabled = YES;
        //不通過用戶交互,是否可以打開窗口
        preferences.javaScriptCanOpenWindowsAutomatically = YES;
        configuration.preferences = preferences;
        
        // 初始化WKWebView
        _webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:configuration];

        // 有兩種代理盟迟,UIDelegate負(fù)責(zé)界面彈窗秋泳,navigationDelegate負(fù)責(zé)加載、跳轉(zhuǎn)等
        _webView.UIDelegate = self;
        _webView.navigationDelegate = self;
    }
    return _webView;
}

2.3 WKNavigationDelegate協(xié)議方法

#pragma mark - WKNavigationDelegate
/* 頁(yè)面開始加載 */
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
/* 開始返回內(nèi)容 */
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
     
}
/* 頁(yè)面加載完成 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
     
}
/* 頁(yè)面加載失敗 */
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
     
}
/* 在發(fā)送請(qǐng)求之前攒菠,決定是否跳轉(zhuǎn) */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    //允許跳轉(zhuǎn)
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允許跳轉(zhuǎn)
    //decisionHandler(WKNavigationActionPolicyCancel);
}
/* 在收到響應(yīng)后迫皱,決定是否跳轉(zhuǎn) */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
     
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    //允許跳轉(zhuǎn)
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允許跳轉(zhuǎn)
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

2.4 UIDelegate協(xié)議的主要方法及應(yīng)用

特別需要注意這個(gè)協(xié)議,與UIWebView不同辖众,在WKWebView中卓起,如果H5頁(yè)面調(diào)用了window對(duì)象的alert,confirm,prompt方法,默認(rèn)不會(huì)有任何反應(yīng)凹炸,它內(nèi)部會(huì)回調(diào)給你既绩,必須由原生這邊實(shí)現(xiàn)相關(guān)的彈窗

感覺這東西設(shè)計(jì)的很雞肋,特別是初學(xué)者很喜歡alert一下看效果还惠,結(jié)果一直點(diǎn)沒反應(yīng)饲握,真是個(gè)大坑

#pragma mark - WKNavigationDelegate
#pragma mark - WKUIDelegate
/// 處理alert彈窗事件
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    
    [self alert:@"溫馨提示" message:message?:@"" buttonTitles:@[@"確認(rèn)"] handler:^(int index, NSString *title) {
       completionHandler();
    }];
}

/// 處理Confirm彈窗事件
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    
    [self alert:@"溫馨提示" message:message?:@"" buttonTitles:@[@"取消", @"確認(rèn)"] handler:^(int index, NSString *title) {
       completionHandler(index != 0);
    }];
}

/// 處理TextInput彈窗事件
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler {
    
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:prompt preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(nil);
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        NSString *text = [alert.textFields firstObject].text;
        NSLog(@"字符串:%@", text);
        completionHandler(text);
    }]];
    [self presentViewController:alert animated:YES completion:nil];
}

#pragma mark - 彈窗
- (void)alert:(NSString *)title message:(NSString *)message {
    [self alert:title message:message buttonTitles:@[@"確定"] handler:nil];
}

- (void)alert:(NSString *)title message:(NSString *)message buttonTitles:(NSArray *)buttonTitles handler:(void(^)(int, NSString *))handler {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    for (int i = 0; i < buttonTitles.count; i++) {
        [alert addAction:[UIAlertAction actionWithTitle:buttonTitles[i] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            if (handler) {
                handler(i, action.title);
            }
        }]];
    }
    [self presentViewController:alert animated:YES completion:nil];
}

3. WKWebView更多實(shí)戰(zhàn)細(xì)節(jié)

3.1 動(dòng)態(tài)更新標(biāo)題

導(dǎo)航欄標(biāo)題經(jīng)常要根據(jù)當(dāng)前H5頁(yè)面標(biāo)題更換,以前都是在頁(yè)面加載完成后蚕键,使用window.document.title來獲取救欧,現(xiàn)在WKWebView提供了相關(guān)字段,我們只需要監(jiān)聽這個(gè)字段即可

[self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
#pragma mark - 屬性監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if ([keyPath isEqualToString:@"title"]) {
        NSString *title = (NSString *)change[NSKeyValueChangeNewKey];
        self.title = title;
    }
}

3.2 動(dòng)態(tài)加載進(jìn)度條

以前UIWKWebView無法獲取加載進(jìn)度锣光,只能知曉開始加載和結(jié)束加載笆怠,因此以前的做法是做一個(gè)假的進(jìn)度條,等到結(jié)束的時(shí)候再突然設(shè)置成100%

WKWebView提供了estimatedProgress來監(jiān)聽加載進(jìn)度誊爹,提供了loading來獲取加載狀態(tài)蹬刷,我們可以拖個(gè)UIProgressView來顯示進(jìn)度(也很多人用layer來做,還可以做漸變的效果频丘,視覺上更優(yōu))

@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL];
[self.webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:NULL];
#pragma mark - 屬性監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        CGFloat estimatedProgress = [change[NSKeyValueChangeNewKey] floatValue];
        NSLog(@"頁(yè)面加載進(jìn)度:%f", estimatedProgress);
        [self.progressView setProgress:estimatedProgress];
    }else if ([keyPath isEqualToString:@"loading"]) {
        BOOL loading = [change[NSKeyValueChangeNewKey] boolValue];
        NSLog(@"%@", loading ? @"開始加載" : @"停止加載");
        self.progressView.hidden = !loading;
    }
}

3.3 獲取已打開頁(yè)面數(shù)量

UIWebView提供了pagecount办成,但是沒有卵用,不準(zhǔn)確搂漠;WKWebView中有backForwardList記錄了可回退的頁(yè)面信息迂卢,已打開頁(yè)面數(shù)量 = backForwardList數(shù)量 + 當(dāng)前1頁(yè)

int pageCount = self.webView.backForwardList.count + 1;

4. JS交互

4.1 原生調(diào)H5

簡(jiǎn)單易用,第一參數(shù)傳執(zhí)行的js方法桐汤,第二個(gè)block中回調(diào)執(zhí)行后的結(jié)果而克,如果沒有返回值,可以忽略這個(gè)block

[self.webView evaluateJavaScript:@"prompt('請(qǐng)輸入您的名字:', '哈利波特')" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        
        if (error) {
            NSLog(@"error: %@", error);
        }else {
            NSLog(@"obj: %@", result);
        }
    }];

4.2 H5調(diào)原生

  1. 在UIWebView中怔毛,H5觸發(fā)原生的函數(shù)员萍,我們普遍做法約定好需要觸發(fā)事件的鏈接規(guī)則,如果是普通超鏈接就放行拣度,如果是特殊鏈接碎绎,就攔截下來蜂莉,然后根據(jù)約定好的規(guī)則,拼湊出要調(diào)用的方法名稱混卵、參數(shù)等等信息
  2. 在WKWebView中映穗,有一套解決js調(diào)用原生方法的規(guī)則

步驟:

  1. window.webkit.messageHandlers.<#對(duì)象名#>.postMessage(<#參數(shù)#>),這個(gè)對(duì)象名稱只是個(gè)別名(不是非要對(duì)應(yīng)我們哪個(gè)對(duì)象名稱)幕随,跟前端協(xié)商好即可蚁滋,比如我這里起名“target”
<script>
    $("#shoot").click(function () {
// 這里按照約定好的規(guī)則,觸發(fā)的時(shí)候按照特定對(duì)象發(fā)送消息赘淮,傳達(dá)到原生中
// 實(shí)際開發(fā)中辕录,還要考慮多端交互的兼容性問題(iOS、Android梢卸、wechat)
        window.webkit.messageHandlers.target.postMessage({action: '開槍射擊'});
    });

    $("#refull").click(function () {
        window.webkit.messageHandlers.target.postMessage({action: '上子彈'});
    });
</script>
  1. 在iOS端走诞,添加js腳本的響應(yīng)對(duì)象

注冊(cè)告訴WKWebView都有哪些對(duì)象要來響應(yīng)js事件,分別叫什么名字

// H5調(diào)用原生格式:window.webkit.messageHandlers.{name}.postMessage(參數(shù));
// window.webkit.messageHandlers.target.postMessage({action: '開槍射擊'});
// 在原生中注冊(cè)好能響應(yīng)js方法的類和注冊(cè)別名蛤高,然后js按照以上方式調(diào)用蚣旱,可以進(jìn)入OC的didReceiveScriptMessage代理方法
[userContentController addScriptMessageHandler:self name:target];
  1. 響應(yīng)對(duì)象實(shí)現(xiàn)相關(guān)協(xié)議

WKWebView會(huì)把觸發(fā)回調(diào)給我們的協(xié)議方法,響應(yīng)對(duì)象實(shí)現(xiàn)它即可

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    
    NSString *name = [NSString stringWithFormat:@"執(zhí)行對(duì)象名稱:%@", message.name];
    NSString *params = [NSString stringWithFormat:@"附帶參數(shù):%@", [message.body description]];
}

4.2.1 H5調(diào)原生的內(nèi)存泄露問題

問題:當(dāng)執(zhí)行addScriptMessageHandler方法時(shí)戴陡,如果傳入的是當(dāng)前控制器塞绿,控制器會(huì)被WKWebView強(qiáng)引用(就算你傳入weak都沒用,內(nèi)部還是轉(zhuǎn)成強(qiáng)引用)恤批,而當(dāng)前控制器強(qiáng)引用著WKWebView异吻,就成了循環(huán)引用

解決方式

方式一

在合適的時(shí)機(jī)添加和移除

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 注冊(cè)響應(yīng)H5調(diào)原生埋點(diǎn)
    WKUserContentController *userContentController = self.webView.configuration.userContentController;
    
    // H5調(diào)用原生格式:window.webkit.messageHandlers.{name}.postMessage(參數(shù));
    // window.webkit.messageHandlers.target.postMessage({action: '開槍射擊'});
    // 在原生中注冊(cè)好能響應(yīng)js方法的類和注冊(cè)別名,然后js按照以上方式調(diào)用喜庞,可以進(jìn)入OC的didReceiveScriptMessage代理方法
    [userContentController addScriptMessageHandler:self name:KCNAME];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 注冊(cè)過的對(duì)象诀浪,移除,否則有內(nèi)存泄露的問題
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:KCNAME];
}

方式二

其實(shí)蘋果這么設(shè)計(jì)延都,應(yīng)該是希望我們傳入一個(gè)單獨(dú)實(shí)現(xiàn)了WKScriptMessageHandler的對(duì)象雷猪,用來響應(yīng)相關(guān)js交互操作,而不是傳入當(dāng)前控制器

參考文章:https://www.cnblogs.com/guohai-stronger/p/10234571.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窄潭,一起剝皮案震驚了整個(gè)濱河市春宣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫉你,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏惋,死亡現(xiàn)場(chǎng)離奇詭異幽污,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)簿姨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門距误,熙熙樓的掌柜王于貴愁眉苦臉地迎上來簸搞,“玉大人,你說我怎么就攤上這事准潭〕每。” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵刑然,是天一觀的道長(zhǎng)寺擂。 經(jīng)常有香客問我,道長(zhǎng)泼掠,這世上最難降的妖魔是什么怔软? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮择镇,結(jié)果婚禮上挡逼,老公的妹妹穿的比我還像新娘。我一直安慰自己腻豌,他們只是感情好家坎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吝梅,像睡著了一般乘盖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上憔涉,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天订框,我揣著相機(jī)與錄音,去河邊找鬼兜叨。 笑死穿扳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的国旷。 我是一名探鬼主播矛物,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼跪但!你這毒婦竟也來了履羞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤屡久,失蹤者是張志新(化名)和其女友劉穎忆首,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體被环,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糙及,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筛欢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浸锨。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唇聘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柱搜,到底是詐尸還是另有隱情迟郎,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布聪蘸,位于F島的核電站宪肖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宇姚。R本人自食惡果不足惜匈庭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浑劳。 院中可真熱鬧阱持,春花似錦、人聲如沸魔熏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蒜绽。三九已至镶骗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躲雅,已是汗流浹背鼎姊。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留相赁,地道東北人相寇。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钮科,于是被迫代替她去往敵國(guó)和親唤衫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355