WKWebView是在Apple的WWDC 2014隨iOS 8和OS X 10.10出來的,是為了解決UIWebView加載速度慢、占用內(nèi)存大的問題。
使用UIWebView加載網(wǎng)頁的時候,我們會發(fā)現(xiàn)內(nèi)存會無限增長,還有內(nèi)存泄漏的問題存在。
WebKit中更新的WKWebView控件的新特性與使用方法,它很好的解決了UIWebView存在的內(nèi)存、加載速度等諸多問題。
一、WKWebView新特性
在性能、穩(wěn)定性、功能方面有很大提升(最直觀的體現(xiàn)就是加載網(wǎng)頁是占用的內(nèi)存);
允許JavaScript的Nitro庫加載并使用(UIWebView中限制);
支持了更多的HTML5特性;
高達(dá)60fps的滾動刷新率以及內(nèi)置手勢;
將UIWebViewDelegate與UIWebView重構(gòu)成了14類與3個協(xié)議查看蘋果官方文檔;
二、WebKit框架概覽
如上圖所示,WebKit框架中最核心的類應(yīng)該屬于WKWebView了,這個類專門用來渲染網(wǎng)頁視圖,其他類和協(xié)議都將基于它和服務(wù)于它。
WKWebView:網(wǎng)頁的渲染與展示,通過WKWebViewConfiguration可以進(jìn)行自定義配置。
WKWebViewConfiguration:這個類專門用來配置WKWebView。
WKPreference:這個類用來進(jìn)行相關(guān)webView設(shè)置。
WKProcessPool:這個類用來配置進(jìn)程池,與網(wǎng)頁視圖的資源共享有關(guān)。
WKUserContentController:這個類主要用來做native與JavaScript的交互管理。
WKUserScript:用于進(jìn)行JavaScript注入。
WKScriptMessageHandler:這個類專門用來處理JavaScript調(diào)用native的方法。
WKNavigationDelegate:網(wǎng)頁跳轉(zhuǎn)間的導(dǎo)航管理協(xié)議,這個協(xié)議可以監(jiān)聽網(wǎng)頁的活動。
WKNavigationAction:網(wǎng)頁某個活動的示例化對象。
WKUIDelegate:用于交互處理JavaScript中的一些彈出框。
WKBackForwardList:堆棧管理的網(wǎng)頁列表。
WKBackForwardListItem:每個網(wǎng)頁節(jié)點(diǎn)對象。
三、WKWebView的屬性
/// webView的自定義配置
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
/// 導(dǎo)航代理
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
/// UI代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
/// 訪問過網(wǎng)頁歷史列表
@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數(shù)據(jù)加載webView視圖
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
/// 返回上一個網(wǎng)頁節(jié)點(diǎn)
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
/// 網(wǎng)頁的標(biāo)題
@property (nullable, nonatomic, readonly, copy) NSString *title;
/// 網(wǎng)頁的URL地址
@property (nullable, nonatomic, readonly, copy) NSURL *URL;
/// 網(wǎng)頁是否正在加載
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
/// 加載的進(jìn)度 范圍為[0, 1]
@property (nonatomic, readonly) double estimatedProgress;
/// 網(wǎng)頁鏈接是否安全
@property (nonatomic, readonly) BOOL hasOnlySecureContent;
/// 證書服務(wù)
@property (nonatomic, readonly, nullable) SecTrustRef serverTrust API_AVAILABLE(macosx(10.12), ios(10.0));
/// 是否可以返回
@property (nonatomic, readonly) BOOL canGoBack;
/// 是否可以前進(jìn)
@property (nonatomic, readonly) BOOL canGoForward;
/// 返回到上一個網(wǎng)頁
- (nullable WKNavigation *)goBack;
/// 前進(jìn)到下一個網(wǎng)頁
- (nullable WKNavigation *)goForward;
/// 重新加載
- (nullable WKNavigation *)reload;
/// 忽略緩存 重新加載
- (nullable WKNavigation *)reloadFromOrigin;
/// 停止加載
- (void)stopLoading;
/// 執(zhí)行JavaScript
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
/// 是否允許左右滑動,返回-前進(jìn)操作 默認(rèn)是NO
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
/// 自定義代理字符串
@property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));
/// 在iOS上默認(rèn)為NO,標(biāo)識不允許鏈接預(yù)覽
@property (nonatomic) BOOL allowsLinkPreview API_AVAILABLE(macosx(10.11), ios(9.0));
/// 滾動視圖
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
/// 是否支持放大手勢,默認(rèn)為NO
@property (nonatomic) BOOL allowsMagnification;
/// 放大因子,默認(rèn)為1
@property (nonatomic) CGFloat magnification;
/// 據(jù)設(shè)置的縮放因子來縮放頁面,并居中顯示結(jié)果在指定的點(diǎn)
- (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的使用
簡單使用,直接加載url地址
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調(diào)用的方法,是通過WKUserContentController類下面的方法:
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
// 創(chuàng)建配置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 創(chuàng)建UserContentController(提供JavaScript向webView發(fā)送消息的方法)
WKUserContentController* userContent = [[WKUserContentController alloc] init];
// 添加消息處理,注意:self指代的對象需要遵守WKScriptMessageHandler協(xié)議,結(jié)束時需要移除
[userContent addScriptMessageHandler:self name:@"NativeMethod"];
// 將UserConttentController設(shè)置到配置文件
config.userContentController = userContent;
// 高端的自定義配置創(chuàng)建WKWebView
WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:config];
// 設(shè)置訪問的URL
NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/reference/webkit"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
[self.view addSubview:webView];
實(shí)現(xiàn)WKScriptMessageHandler協(xié)議方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
// 判斷是否是調(diào)用原生的
if ([@"NativeMethod" isEqualToString:message.name]) {
// 判斷message的內(nèi)容,然后做相應(yīng)的操作
if ([@"close" isEqualToString:message.body]) {
}
}
}
注意:上面將當(dāng)前ViewController設(shè)置為MessageHandler之后需要在當(dāng)前ViewController銷毀前將其移除,否則會造成內(nèi)存泄漏。
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];
五、WKNavigationDelegate代理方法
如果實(shí)現(xiàn)了代理方法,一定要在decidePolicyForNavigationAction和decidePolicyForNavigationResponse方法中的回調(diào)設(shè)置允許跳轉(zhuǎn)。
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
WKNavigationActionPolicyCancel, // 取消跳轉(zhuǎn)
WKNavigationActionPolicyAllow, // 允許跳轉(zhuǎn)
} API_AVAILABLE(macosx(10.10), ios(8.0));
// 1 在發(fā)送請求之前,決定是否跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSLog(@"1-------在發(fā)送請求之前,決定是否跳轉(zhuǎn) -->%@",navigationAction.request);
decisionHandler(WKNavigationActionPolicyAllow);
}
// 2 頁面開始加載時調(diào)用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"2-------頁面開始加載時調(diào)用");
}
// 3 在收到響應(yīng)后,決定是否跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
/// 在收到服務(wù)器的響應(yīng)頭,根據(jù)response相關(guān)信息,決定是否跳轉(zhuǎn)。decisionHandler必須調(diào)用,來決定是否跳轉(zhuǎn),參數(shù)WKNavigationActionPolicyCancel取消跳轉(zhuǎn),WKNavigationActionPolicyAllow允許跳轉(zhuǎn)
NSLog(@"3-------在收到響應(yīng)后,決定是否跳轉(zhuǎn)");
decisionHandler(WKNavigationResponsePolicyAllow);
}
// 4 當(dāng)內(nèi)容開始返回時調(diào)用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(@"4-------當(dāng)內(nèi)容開始返回時調(diào)用");
}
// 5 頁面加載完成之后調(diào)用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSLog(@"5-------頁面加載完成之后調(diào)用");
}
// 6 頁面加載失敗時調(diào)用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"6-------頁面加載失敗時調(diào)用");
}
// 接收到服務(wù)器跳轉(zhuǎn)請求之后調(diào)用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"-------接收到服務(wù)器跳轉(zhuǎn)請求之后調(diào)用");
}
// 數(shù)據(jù)加載發(fā)生錯誤時調(diào)用
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"----數(shù)據(jù)加載發(fā)生錯誤時調(diào)用");
}
// 需要響應(yīng)身份驗(yàn)證時調(diào)用 同樣在block中需要傳入用戶身份憑證
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
//用戶身份信息
NSLog(@"----需要響應(yīng)身份驗(yàn)證時調(diào)用 同樣在block中需要傳入用戶身份憑證");
NSURLCredential *newCred = [NSURLCredential credentialWithUser:@""
password:@""
persistence:NSURLCredentialPersistenceNone];
// 為 challenge 的發(fā)送方提供 credential
[[challenge sender] useCredential:newCred forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
// 進(jìn)程被終止時調(diào)用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"----------進(jìn)程被終止時調(diào)用");
}
六、WKUIDelegate代理方法
/**
* web界面中有彈出警告框時調(diào)用
*
* @param webView 實(shí)現(xiàn)該代理的webview
* @param message 警告框中的內(nèi)容
* @param completionHandler 警告框消失調(diào)用
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler {
NSLog(@"-------web界面中有彈出警告框時調(diào)用");
}
// 創(chuàng)建新的webView時調(diào)用的方法
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
NSLog(@"-----創(chuàng)建新的webView時調(diào)用的方法");
return webView;
}
// 關(guān)閉webView時調(diào)用的方法
- (void)webViewDidClose:(WKWebView *)webView {
NSLog(@"----關(guān)閉webView時調(diào)用的方法");
}
// 下面這些方法是交互JavaScript的方法
// 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 {
NSLog(@"%@",message);
completionHandler(YES);
}
// 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{
NSLog(@"%@",prompt);
completionHandler(@"123");
}
// 默認(rèn)預(yù)覽元素調(diào)用
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo {
NSLog(@"-----默認(rèn)預(yù)覽元素調(diào)用");
return YES;
}
// 返回一個視圖控制器將導(dǎo)致視圖控制器被顯示為一個預(yù)覽。返回nil將WebKit的默認(rèn)預(yù)覽的行為。
- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions {
NSLog(@"----返回一個視圖控制器將導(dǎo)致視圖控制器被顯示為一個預(yù)覽。返回nil將WebKit的默認(rèn)預(yù)覽的行為。");
return self;
}
// 允許應(yīng)用程序向它創(chuàng)建的視圖控制器彈出
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController {
NSLog(@"----允許應(yīng)用程序向它創(chuàng)建的視圖控制器彈出");
}
// 顯示一個文件上傳面板。completionhandler完成處理程序調(diào)用后打開面板已被撤銷。通過選擇的網(wǎng)址,如果用戶選擇確定,否則為零。如果不實(shí)現(xiàn)此方法,Web視圖將表現(xiàn)為如果用戶選擇了取消按鈕。
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler {
NSLog(@"----顯示一個文件上傳面板");
}