WKWebView是ios 8 出來的,是為了解決UIWebView卡慢,占用內存過大的問題。
在以往時候,如果用UIWebView加載加載網頁的時候,卡慢現象會很嚴重,有時候往往會卡到一個頁面無法動彈,空白屏時間過長,基本上沒有什么體驗可言;
WebKit中的WKWebView控件的新特性和使用方法,較好的解決了卡、慢、占用內存過大的問題。而且支持了更多HTML5的特性,還有JS庫的加載和使用,功能比UIWebView強大了很多。
一、概述
如上圖所示,WebKit框架中最核心的類應該屬於WKWebView了,這個類專門用來渲染網頁視圖,其他類和協議都將基於它和服務於它。
WKWebView:網頁的渲染與展示,通過WKWebViewConfiguration可以進行自定義配置。
WKWebViewConfiguration:這個類專門用來配置WKWebView。
WKPreference:這個類用來進行相關webView設置。
WKProcessPool:這個類用來配置進程池,與網頁視圖的資源共享有關。
WKUserContentController:這個類主要用來做native與JavaScript的交互管理。
WKUserScript:用於進行JavaScript注入。
WKScriptMessageHandler:這個類專門用來處理JavaScript調用native的方法。
WKNavigationDelegate:網頁跳轉間的導航管理協議,這個協議可以監聽網頁的活動。
WKNavigationAction:網頁某個活動的示例化對象。
WKUIDelegate:用於交互處理JavaScript中的一些彈出框。
WKBackForwardList:堆棧管理的網頁列表。
WKBackForwardListItem:每個網頁節點對象。
二、相關的屬性
/// webView的自定義配置 @property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration; /// 導航代理 @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate; /// UI代理 @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate; /// 訪問過網頁歷史列表 @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList; /// 自定義初始化webView - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER; - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; /// url加載webView視圖 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request; /// 文件加載webView視圖 - (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0)); /// HTMLString字符串加載webView視圖 - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; /// NSData數據加載webView視圖 - (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0)); /// 返回上一個網頁節點 - (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item; /// 網頁的標題 @property (nullable, nonatomic, readonly, copy) NSString *title; /// 網頁的URL地址 @property (nullable, nonatomic, readonly, copy) NSURL *URL; /// 網頁是否正在加載 @property (nonatomic, readonly, getter=isLoading) BOOL loading; /// 加載的進度 范圍為[0, 1] @property (nonatomic, readonly) double estimatedProgress; /// 網頁鏈接是否安全 @property (nonatomic, readonly) BOOL hasOnlySecureContent; /// 證書服務 @property (nonatomic, readonly, nullable) SecTrustRef serverTrust API_AVAILABLE(macosx(10.12), ios(10.0)); /// 是否可以返回 @property (nonatomic, readonly) BOOL canGoBack; /// 是否可以前進 @property (nonatomic, readonly) BOOL canGoForward; /// 返回到上一個網頁 - (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; /// 是否允許左右滑動,返回-前進操作 默認是NO @property (nonatomic) BOOL allowsBackForwardNavigationGestures; /// 自定義代理字符串 @property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0)); /// 在iOS上默認為NO,標識不允許鏈接預覽 @property (nonatomic) BOOL allowsLinkPreview API_AVAILABLE(macosx(10.11), ios(9.0)); /// 滾動視圖 @property (nonatomic, readonly, strong) UIScrollView *scrollView; /// 是否支持放大手勢,默認為NO @property (nonatomic) BOOL allowsMagnification; /// 放大因子,默認為1 @property (nonatomic) CGFloat magnification; /// 據設置的縮放因子來縮放頁面,並居中顯示結果在指定的點 - (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point; /// 證書列表 @property (nonatomic, readonly, copy) NSArray *certificateChain API_DEPRECATED_WITH_REPLACEMENT("serverTrust", macosx(10.11, 10.12), ios(9.0, 10.0));
三、使用
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds]; [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://developer.apple.com/reference/webkit"]]]; [self.view addSubview:webView];
自定義配置
再WKWebView里面注冊供JS調用的方法,是通過WKUserContentController類下面的方法:
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
自定義配置 再WKWebView里面注冊供JS調用的方法,是通過WKUserContentController類下面的方法: - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
實現WKScriptMessageHandler協議方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { // 判斷是否是調用原生的 if ([@"NativeMethod" isEqualToString:message.name]) { // 判斷message的內容,然后做相應的操作 if ([@"close" isEqualToString:message.body]) { } } }
注意:上面將當前ViewController設置為MessageHandler之后需要在當前ViewController銷毀前將其移除,否則會造成內存泄漏。
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];
四、WKNavigationDelegate代理方法
如果實現了代理方法,一定要在decidePolicyForNavigationAction和decidePolicyForNavigationResponse方法中的回調設置允許跳轉。
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) { WKNavigationActionPolicyCancel, // 取消跳轉 WKNavigationActionPolicyAllow, // 允許跳轉 } API_AVAILABLE(macosx(10.10), ios(8.0));
// 1 在發送請求之前,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@"1-------在發送請求之前,決定是否跳轉 -->%@",navigationAction.request); decisionHandler(WKNavigationActionPolicyAllow); } // 2 頁面開始加載時調用 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"2-------頁面開始加載時調用"); } // 3 在收到響應后,決定是否跳轉 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { /// 在收到服務器的響應頭,根據response相關信息,決定是否跳轉。decisionHandler必須調用,來決定是否跳轉,參數WKNavigationActionPolicyCancel取消跳轉,WKNavigationActionPolicyAllow允許跳轉 NSLog(@"3-------在收到響應后,決定是否跳轉"); decisionHandler(WKNavigationResponsePolicyAllow); } // 4 當內容開始返回時調用 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { NSLog(@"4-------當內容開始返回時調用"); } // 5 頁面加載完成之后調用 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSLog(@"5-------頁面加載完成之后調用"); } // 6 頁面加載失敗時調用 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"6-------頁面加載失敗時調用"); } // 接收到服務器跳轉請求之后調用 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation { NSLog(@"-------接收到服務器跳轉請求之后調用"); } // 數據加載發生錯誤時調用 - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { NSLog(@"----數據加載發生錯誤時調用"); } // 需要響應身份驗證時調用 同樣在block中需要傳入用戶身份憑證 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { //用戶身份信息 NSLog(@"----需要響應身份驗證時調用 同樣在block中需要傳入用戶身份憑證"); NSURLCredential *newCred = [NSURLCredential credentialWithUser:@"" password:@"" persistence:NSURLCredentialPersistenceNone]; // 為 challenge 的發送方提供 credential [[challenge sender] useCredential:newCred forAuthenticationChallenge:challenge]; completionHandler(NSURLSessionAuthChallengeUseCredential,newCred); } // 進程被終止時調用 - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView { NSLog(@"----------進程被終止時調用"); }
五、WKUIDelegate代理方法
/** * web界面中有彈出警告框時調用 * * @param webView 實現該代理的webview * @param message 警告框中的內容 * @param completionHandler 警告框消失調用 */ - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler { NSLog(@"-------web界面中有彈出警告框時調用"); } // 創建新的webView時調用的方法 - (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { NSLog(@"-----創建新的webView時調用的方法"); return webView; } // 關閉webView時調用的方法 - (void)webViewDidClose:(WKWebView *)webView { NSLog(@"----關閉webView時調用的方法"); } // 下面這些方法是交互JavaScript的方法 // JavaScript調用confirm方法后回調的方法 confirm是js中的確定框,需要在block中把用戶選擇的情況傳遞進去 -(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler { NSLog(@"%@",message); completionHandler(YES); } // JavaScript調用prompt方法后回調的方法 prompt是js中的輸入框 需要在block中把用戶輸入的信息傳入 -(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{ NSLog(@"%@",prompt); completionHandler(@"123"); } // 默認預覽元素調用 - (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo { NSLog(@"-----默認預覽元素調用"); return YES; } // 返回一個視圖控制器將導致視圖控制器被顯示為一個預覽。返回nil將WebKit的默認預覽的行為。 - (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions { NSLog(@"----返回一個視圖控制器將導致視圖控制器被顯示為一個預覽。返回nil將WebKit的默認預覽的行為。"); return self; } // 允許應用程序向它創建的視圖控制器彈出 - (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController { NSLog(@"----允許應用程序向它創建的視圖控制器彈出"); } // 顯示一個文件上傳面板。completionhandler完成處理程序調用后打開面板已被撤銷。通過選擇的網址,如果用戶選擇確定,否則為零。如果不實現此方法,Web視圖將表現為如果用戶選擇了取消按鈕。 - (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler { NSLog(@"----顯示一個文件上傳面板"); }
附:
官方文檔地址:https://developer.apple.com/documentation/webkit?language=objc
美團技術團隊WebView性能、體驗分析與優化:https://tech.meituan.com/WebViewPerf.html?utm_source=tool.lu