WebKit 框架概览

WKWebView 是 iOS8 中引入的新组建,苹果将 UIWebViewDelegate 与 UIWebView 重构成了14个类和3个协议并引入了不少新的功能和接口,它代替了 UIKit 中的 UIWebView 和 Appkit 中的 WebView,提供了统一的跨双平台 API。(iOS 和 macOS)
并且最吸引人的在于很多方法和 UIWebView 类似,从 UIWebView 转到 WKWebView 可以很迅速,在不考虑适配 iOS8 系统以下的情况下,WKWebView 是一个很不错的选择。它的新特性包括:
- 在性能、稳定性、功能方面有很大的提升,最能直观的体现就是加载网页时占用的内存,在模拟器加载百度时,WKWebView 占用23M,而 UIWebView 占用85M;
- 和 Safari 相同的 Javascript 引擎,允许 Javascript 的 Nitro 库加载并使用;(UIWebView 中限制)
- 支持了更多的 HTML5 特征;
- 60fps刷新率,内置手势。

如上图所示,WebKit 框架中最核心的类应该属于 WKWebView 了,这个类专门用来渲染网页视图,其他类和协议都将基于它和服务于它。
- WKWebView:网页的渲染与展示,通过 WKWebViewConfiguration 可以进行自定义配置。
- WKWebViewConfiguration:这个类专门用来配置 WKWebView。
- WKPreference:这个类用来进行相关 webView 设置。
- WKProcessPool:这个类用来配置进程池,与网页视图的资源共享有关。
- WKUserContentController:这个类主要用来做 native 与 JavaScript 的交互管理。
- WKUserScript:用于进行 JavaScript 注入。
- WKScriptMessageHandler:这个类专门用来处理 JavaScript 调用 native 的方法。
- WKNavigationDelegate:网页跳转间的导航管理协议,这个协议可以监听网页的活动。
- WKNavigationAction:网页某个活动的示例化对象。
- WKUIDelegate:用于交互处理 JavaScript 中的一些弹出框。
- WKBackForwardList:堆栈管理的网页列表。
- WKBackForwardListItem:每个网页节点对象。
WKWebKit 的属性及方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| /// 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;
|
用来追踪加载过程(页面开始加载、加载完成、加载失败)的方法:
1 2 3 4 5 6 7 8
| // 页面开始加载时调用 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation; // 当内容开始返回时调用 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation; // 页面加载完成之后调用 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation; // 页面加载失败时调用 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
|
页面跳转的代理方法:
1 2 3 4 5 6
| // 接收到服务器跳转请求之后调用 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation; // 在收到响应后,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler; // 在发送请求之前,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
|
WKNavigationDelegate 的调用顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| // 在发送请求之前,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ decisionHandler(WKNavigationActionPolicyAllow); }
// 页面开始加载时调用 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{ }
// 在发送请求之前,决定是否跳转 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
decisionHandler(WKNavigationActionPolicyAllow); }
// 接收到服务器跳转请求之后调用 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
}
// 权限认证的时候调用 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler{ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling ,nil); }
/**** 以下三个是连续调用 ****/
// 在收到响应后,决定是否跳转和发送请求之前那个允许配套使用 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
decisionHandler(WKNavigationResponsePolicyAllow); }
// 当内容开始返回时调用 - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{ }
// 页面加载完成之后调用 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{ }
|
WKWebView 调用 JavaScript
前提:必须导入 #import <WebKit/WebKit.h>
,遵循 WKNavigationDelegate
代理方法
在 WKWebView 中调用 JavaScript 有两个方案:
1、在初始化 WebView 的时候加在配置文件中引入 JavaScript 的方法
2、在恰当的时机执行 evaluateJavaScript:completionHandler:
方法
方法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 举个例子,我们在这里实现 WKWebView 图片自适应屏幕宽度,设置网页最小字体大小为14.5
// JavaScript 的代码 NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta); var imgs = document.getElementsByTagName('img');for (var i in imgs){imgs[i].style.maxWidth='100%';imgs[i].style.height='auto';} ";
// 注入 JavaScript WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; WKUserContentController *wkUController = [[WKUserContentController alloc] init]; [wkUController addUserScript:wkUScript];
WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init]; wkWebConfig.userContentController = wkUController; wkWebConfig.allowsInlineMediaPlayback = YES; if ([UIDevice currentDevice].systemVersion >= 9.0) { wkWebConfig.allowsPictureInPictureMediaPlayback = YES; }
// 设置网页最小字体大小为14.5 WKPreferences *preference = [[WKPreferences alloc]init]; preference.minimumFontSize = 14.5; wkWebConfig.preferences = preference;
_webView = [[WKWebView alloc]initWithFrame:CGRectNull configuration:wkWebConfig]; [_webView setFrame:[UIScreen mainScreen].bounds]; _webView.scrollView.showsVerticalScrollIndicator = NO; _webView.scrollView.showsHorizontalScrollIndicator = NO; _webView.navigationDelegate = self; [self.view addSubview:_webView];
|
方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { // 禁止 WKWebView 的捏合手势,双击放大缩小等操作 NSString *javascript = @"var meta = document.createElement('meta');meta.setAttribute('name', 'viewport');meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');document.getElementsByTagName('head')[0].appendChild(meta);"; [webView evaluateJavaScript:javascript completionHandler:nil]; }
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { // 修改 WKWebView 中的所有字体颜色为红色 [webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#ff0000'" completionHandler:nil]; // 修改网页中 table 标签,把表格的线宽改为5px,线的颜色改为红色 NSString *jScript = @"function compatTable(){var tableElements=document.getElementsByTagName(\"table\");for(var i=0;i<tableElements.length;i++){var tableElement=tableElements[i];tableElement.cellspacing=\"\";tableElement.cellpadding=\"\";tableElement.border=\"\";tableElement.setAttribute(\"style\",\"border-collapse:collapse; display:table;\")}var tdElements=document.getElementsByTagName(\"td\");for(var i=0;i<tdElements.length;i++){var tdElement=tdElements[i];tdElement.valign=\"\";tdElement.width=\"\";tdElement.setAttribute(\"style\",\"border:5px solid red;\");tdElement.setAttribute(\"contenteditable\",\"false\")}};compatTable();"; [webView evaluateJavaScript:jScript completionHandler:nil]; }
|
当然了,上面举了三个案例,细心的读者会有发现 completionHandler:
这个回调方法,似乎都返回的 nil,没有做任何回调。那么下面我在列举一个比较常见的场景 - 获取当前点中的图像链接以及网页中的所有链接。
遵循 WKNavigationDelegate 代理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| // self.imgUrls 是一个 NSArray 的属性 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSString *getImgUrlsJS = @"\ function getImgUrls() {\ var imgs = document.getElementsByTagName('img');\ var urls = [];\ for (var i = 0; i < imgs.length; i++) {\ var img = imgs[i];\ urls[i] = img.src;\ }\ return urls;\ }"; [webView evaluateJavaScript:getImgUrlsJS completionHandler:nil]; [webView evaluateJavaScript:@"getImgUrls()" completionHandler:^(id _Nullable obj, NSError * _Nullable error) { NSLog(@"网页中的所有图像链接:%@", obj); self.imgUrls = obj; }]; NSString *imgClickJS = @"function imgClickAction(){var imgs=document.getElementsByTagName('img');var length=imgs.length;for(var i=0; i < length;i++){img=imgs[i];if(\"ad\" ==img.getAttribute(\"flag\")){var parent = this.parentNode;if(parent.nodeName.toLowerCase() != \"a\")return;}img.onclick=function(){window.location.href='image-preview:'+this.src}}}"; [webView evaluateJavaScript:imgClickJS completionHandler:nil]; [webView evaluateJavaScript:@"imgClickAction()" completionHandler:nil]; }
// self.imgUrls 是一个 NSArray 的属性 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSString *url = navigationAction.request.URL.absoluteString; if ([url hasPrefix:@"image-preview:"]) { NSString *imgUrl = [url substringFromIndex:14]; NSInteger index = [self.imgUrls indexOfObject:imgUrl]; NSLog(@"当前点中的图像链接是:%@",self.imgUrls[index]); decisionHandler(WKNavigationActionPolicyCancel); return; } decisionHandler(WKNavigationActionPolicyAllow); }
|
WKUIDelegate 的回调方法
假如网页中含有一些 JavaScript 的操作执行,这个时候就触发它的另一个代理 WKUIDelegate
的回调方法。
1 2 3 4 5 6 7 8
| // 在 JS 端调用 alert 函数时,会触发此代理方法。 - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler; // JS 端调用 confirm 函数时,会触发此代理方法。 - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler; // JS 端调用 prompt 函数时,会触发此代理方法。 - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
|
特别注意:以上方法需要在方法的结束写上回调。
JavaScript 中调用 Objective-C 的方法
当 Web 端想传一些数据给 iOS,那它们会调用 messageHandlers: postMessage:
方法来发送给我们。
此时我们所需要做的就是遵循 WKScriptMessageHandler
的代理方法,name 和上方 JavaScript 中的方法名相对应。
1
| - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
|
具体操作如下:
1 2 3 4 5 6 7
| [[_webView configuration].userContentController addScriptMessageHandler:self name:@"方法名"];
// WKScriptMessageHandler协议方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { }
|