WKWebView API
WKWebView 对象可以显示交互式Web内容,例如应用内浏览器。你可以使用 WKWebView 类将Web内容嵌入到你的应用程序中。 为此,创建一个 WKWebView 对象,像设置视图一样设置它,并向其发送加载Web内容的请求。
预览
重要
从 iOS 8.0 和 OS X 10.10 开始,在你的APP中使用 WKWebView 添加网页内容,不要使用 UIWebView 或 WebView。
使用 initWithFrame:configuration:
方法创建了一个新的 WKWebView 对象之后,你需要加载web内容。使用 loadHTMLString:baseURL:
方法加载本地 HTML 文件或者使用 loadRequest:
方法开始加载 web 内容。使用 stopLoading
方法停止加载,并且使用 loading
属性查看 web 视图是否正在加载中。为对象设置委托属性以遵守 WKUIDelegate 协议,以跟踪Web内容的加载。
要允许用户在 web 历史页面中前进或者后退,为按钮设置使用 goBack
和goForward
方法的动作。当用户不能在某个方向上再移动时,使用canGoBack
和canGoForward
属性禁用按钮。
默认情况下,web 视图会自动将出现在 web 内容中的电话号码转换为电话链接。当电话链接被点击时,电话应用程序就会启动并拨打该号码。要关闭这个默认的行为,用 WKDataDetectorTypes
设置 dataDetectorTypes
属性以不包含 WKDataDetectorTypePhoneNumber
标志。
你还可以使用 setMagnification:centeredAtPoint:
以编程方式设置Web内容第一次在Web视图中显示的缩放比例。 此后,用户可以使用手势来改变比例。
Symbols
初始化web视图
-
configuration
用于初始化Web视图的配置副本。
-
- initWithFrame:configuration:
用指定的 frame 和 configuration 初始化web视图 。
- initWithCoder:
查看web信息
-
scrollView
与web视图相关联的滚动视图。
-
title
页面标题。
-
URL
活动网址。
-
customUserAgent
自定义用户代理字符串。
-
serverTrust
用于当前待导航的 SecTrustRef 对象。
设置委托
-
navigationDelegate
web视图的导航代理。
-
UIDelegate
web视图的用户界面委托。
加载内容
-
estimatedProgress
当前导航的哪几个部分已经加载的估计值(double:0.0~1.0)。
-
hasOnlySecureContent
布尔值,指示页面上的所有资源是否通过安全加密的连接加载。
-
- loadHTMLString:baseURL:
设置网页内容和 base URL。
-
loading
布尔值,显示当前视图是否正在加载。
-
- reload
重新加载当前页面
-
- reloadFromOrigin
重新加载当前页面,如果可能,使用缓存验证条件执行端到端重新验证。
-
- stopLoading
停止加载当前页面所有资源。
-
- loadData:MIMEType:characterEncodingName:baseURL:
设置网页内容和 base URL。
-
- loadFileURL:allowingReadAccessToURL:
找到文件系统上所请求的文件URL
缩放内容
-
allowsMagnification
布尔值,表示放大手势是否会改变网页视图的放大倍数。
-
magnification
页面内容当前缩放因子。
-
- setMagnification:centeredAtPoint:
按指定的因子缩放页面内容,并将结果居中在指定的点上。
导航
-
allowsBackForwardNavigationGestures
布尔值,指示水平滑动手势是否会触发后退列表导航。默认为:NO。
-
backForwardList
网页视图的后退列表。(之前访问过的 web 页面的列表)
-
canGoBack
布尔值,指示后退列表中是否有可被导航到的后退项。
-
canGoForward
布尔值,指示后退列表中是否有可导航的前进项。
-
allowsLinkPreview
布尔值,用于确定是否按下链接可以显示链接目标的预览。
-
- goBack
导航到后退列表中的后退项中。
-
- goForward
导航到后退列表中的前进项中。
-
- goToBackForwardListItem:
导航到后退列表中的某一个网页项,并将其设置为当前项。
-
- loadRequest:
导航到请求的URL地址
执行 JavaScript
-
- evaluateJavaScript:completionHandler:
评估 JavaScript 字符串。Objective-C 调用 JavaScript 方法。
实例方法
-
- goBack
导航到后退列表中的后退项中。
-
- goForward
导航到后退列表中的前进项中。
-
- reload
重新加载当前页面
-
- reloadFromOrigin
重新加载当前页面,如果可能,使用缓存验证条件执行端到端重新验证。
-
- stopLoading
停止加载当前页面所有资源。
WKWebViewConfiguration API
用于初始化Web视图的属性集合。
预览
使用 WKWebViewConfiguration 类,你可以确定网页呈现的速度,媒体播放的处理方式,用户可以选择的项目的粒度等等。
WKWebViewConfiguration 仅在首次初始化Web视图时使用。 当web视图被创建以后,你就无法再使用此类来更改Web视图的配置信息了。
Symbols
配置新的Web视图的属性
-
applicationNameForUserAgent
在用户代理字符串中使用的应用程序的名称。
-
preferences
Web视图要使用的首选项对象。
-
processPool
从中获取视图的Web内容进程的进程池。
-
userContentController
与网页视图关联的用户内容控制器。
-
websiteDataStore
由网页视图使用的存储的网站数据。
确定页面可扩展性
-
ignoresViewportScaleLimits
布尔值,用于确定 WKWebView 对象是否应始终允许缩放网页。
设置渲染首选项
-
suppressesIncrementalRendering
布尔值,指示网络视图是否在【内容渲染完全加载到内存之前】禁止内容呈现。默认值:NO。
设置媒体播放首选项
-
allowsInlineMediaPlayback
布尔值,指示HTML5视频是否内嵌播放或使用native全屏控制器。
-
allowsAirPlayForMediaPlayback
是否允许 AirPlay。
-
allowsPictureInPictureMediaPlayback
HTML5视频是否可以播放画中画。
-
mediaTypesRequiringUserActionForPlayback
确定哪些媒体类型需要用户手势才能开始播放。
-
WKAudiovisualMediaTypes
枚举类型:需要用户手势开始播放的媒体类型
typedef enum WKAudiovisualMediaTypes : NSUInteger { WKAudiovisualMediaTypeNone = 0, WKAudiovisualMediaTypeAudio = 1 << 0, WKAudiovisualMediaTypeVideo = 1 << 1, WKAudiovisualMediaTypeAll = NSUIntegerMax } WKAudiovisualMediaTypes;
设置选择粒度
-
selectionGranularity
用户可以在网页视图中交互地选择内容的粒度级别。
-
WKSelectionGranularity
枚举类型:交互式创建和修改选择的粒度。
typedef enum WKSelectionGranularity : NSInteger { WKSelectionGranularityDynamic, //选择粒度根据选择而自动变化。 WKSelectionGranularityCharacter //选择端点可以放置在任何字符边界上 } WKSelectionGranularity;
选择用户界面方向
-
userInterfaceDirectionPolicy
用户界面元素的方向。
-
WKUserInterfaceDirectionPolicy
枚举类型:用于确定Web视图中用户界面元素的方向性策略。
typedef enum WKUserInterfaceDirectionPolicy : NSInteger { WKUserInterfaceDirectionPolicyContent, // 方向遵循CSS / HTML / XHTML规范 WKUserInterfaceDirectionPolicySystem // 方向遵循视图的userInterfaceLayoutDirection属性 } WKUserInterfaceDirectionPolicy;
识别数据类型
-
dataDetectorTypes
所需的数据检测类型。
-
WKDataDetectorTypes
枚举类型:检测到的数据类型。
typedef enum WKDataDetectorTypes : NSUInteger { WKDataDetectorTypeNone = 0, WKDataDetectorTypePhoneNumber = 1 << 0, WKDataDetectorTypeLink = 1 << 1, WKDataDetectorTypeAddress = 1 << 2, WKDataDetectorTypeCalendarEvent = 1 << 3, WKDataDetectorTypeTrackingNumber = 1 << 4, WKDataDetectorTypeFlightNumber = 1 << 5, WKDataDetectorTypeLookupSuggestion = 1 << 6, WKDataDetectorTypeAll = NSUIntegerMax, WKDataDetectorTypeSpotlightSuggestion = WKDataDetectorTypeLookupSuggestion } WKDataDetectorTypes;
WKNavigationDelegate API
WKNavigationDelegate 协议方法可以帮助你实现在Web视图接受,加载和完成导航请求的过程中触发的自定义行为。
初始化导航和跟踪加载进度
// 页面开始加载web内容时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 当web内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用 ( 【web视图加载内容时】发生错误)
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error;
// 【web视图导航过程中发生错误】时调用。
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;
// 当Web视图的Web内容进程终止时调用。
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;
- 导航错误:NSURLErrorDomain Code=-999
原因:webview 的上一个请求还没有加载完成,下一个请求发起了,此时 webview 会取消掉之前的请求,因此会回调导航失败错误(NSURLErrorCancelled = -999)。
响应服务器操作
// 收到服务器重定向之后调用 (接收到服务器跳转请求)
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
实例方法
- webView:didReceiveAuthenticationChallenge:completionHandler:
当 Web 视图需要响应认证挑战(authentication challenge)时调用。
当使用 Https 协议加载web内容时,使用的证书不合法或者证书过期时需要使用该方法:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
允许导航
// 通常用于处理跨域的链接能否导航
// 在发送请求之前,决定是否允许或取消导航。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSLog(@"%s", __FUNCTION__);
// 如果请求的是百度地址,则允许跳转
if ([navigationAction.request.URL.host.lowercaseString isEqual:@"www.baidu.com"]) {
// 允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// 否则不允许跳转
decisionHandler(WKNavigationActionPolicyCancel);
}
// 收到响应后,决定是否允许或取消导航。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
NSLog(@"%s", __FUNCTION__);
// 如果响应的地址是百度,则允许跳转
if ([navigationResponse.response.URL.host.lowercaseString isEqual:@"www.baidu.com"]) {
// 允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
return;
}
// 否则不允许跳转
decisionHandler(WKNavigationResponsePolicyCancel);
}
导航策略
-
WKNavigationActionPolicy
从
decisionPolicyForNavigationAction:decisionHandler:
方法返回的决定策略。typedef enum WKNavigationActionPolicy : NSInteger { WKNavigationActionPolicyCancel, WKNavigationActionPolicyAllow } WKNavigationActionPolicy;
-
WKNavigationResponsePolicy
从
webView:decidePolicyForNavigationResponse:decisionHandler:
方法返回的决定策略。typedef enum WKNavigationResponsePolicy : NSInteger { WKNavigationResponsePolicyCancel, WKNavigationResponsePolicyAllow } WKNavigationResponsePolicy;
WKUIDelegate API
WKUIDelegate 类是网页视图的用户界面委托协议,提供了代表网页呈现本机用户界面元素的方法。
Web视图用户界面委托对象实现此协议来控制打开的新窗口,增加用户单击元素时显示的默认菜单项的行为,并执行其他与用户界面相关的任务。 可以通过处理 JavaScript 或其他插件内容来调用这些方法。 默认Web视图实现假定每个Web视图有一个窗口,因此非常规用户界面可能会实现用户界面委托。
参考:【iOS 开发】WKWebView 学习笔记 (3)-WKUIDelegate
Symbols
创建web视图
-
- webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:
创建一个新的 web 视图
显示UI面板
-
- webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:
显示一个 JavaScript 警告面板。
-
- webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:
显示一个 JavaScript 确认面板。
-
- webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:
显示一个 JavaScript 文本输入面板。
关闭web视图
-
webViewDidClose:
通知您的应用程序DOM窗口成功关闭。
显示上传面板
-
- webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:
显示文件上传面板。
响应强制触控动作
-
- webView:shouldPreviewElement:
确定给定元素是否应显示预览。
-
- webView:previewingViewControllerForElement:defaultActions:
当用户执行窥视操作时调用。
-
- webView:commitPreviewingViewController:
当用户在预览中执行弹出操作时调用。
OC 原生显示 JS 弹窗的正确方法
参考: WKWebView completionHandler called before dismissal @stackoverflow
// 显示一个 JavaScript 警告弹窗
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AlertPanel" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// ⚠️必须在这里回调 JavaScript
completionHandler();
}];
[alertController addAction:alertAction];
[self presentViewController:alertController animated:YES completion:nil];
}
WKUIDelegate 示例代码:
#pragma mark - WKUIDelegate
// 创建新的webView
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
WKFrameInfo *frameInfo = navigationAction.targetFrame;
if (![frameInfo isMainFrame]) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
// 显示 JavaScript 警告框
// 测试JS代码:alert("alert message!")
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
[UIAlertController showAlertInViewController:self withTitle:@"温馨提示" message:message cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:@[@"确认"] tapBlock:^(UIAlertController * _Nonnull controller, UIAlertAction * _Nonnull action, NSInteger buttonIndex) {
completionHandler();
}];
}
// 确认框
// 测试JS代码:confirm("confirm message!")
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
[UIAlertController showAlertInViewController:self withTitle:@"需要再次确认" message:message cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@[@"确认"] tapBlock:^(UIAlertController * _Nonnull controller, UIAlertAction * _Nonnull action, NSInteger buttonIndex) {
if (buttonIndex == 0) {
completionHandler(NO);
}else {
completionHandler(YES);
}
}];
}
WKScriptMessageHandler API
遵守 WKScriptMessageHandler 协议的类,提供了从网页中运行 JavaScript 接收消息的方法。
// 从 web 界面中接收到一个脚本时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
这个协议只包含以上一个必须实现的方法,这个方法是提高App与web端交互的关键,它可以直接将接收到的JS脚本转为 OC 或 Swift 对象。(当然,在 UIWebView 也可以通过“曲线救国”的方式与web进行交互,著名的Cordova 框架就是这种机制)
——WKWebView的新特性与使用 @saitjr
这个 API 真正神奇的地方在于 JavaScript 对象可以自动转换为 Objective-C 或 Swift 对象。
——WKWebView @nshipster
在 JavaScript 端通过
window.webkit.messageHandlers.{InjectedName}.postMessage()
方法来发送消息到native。我们需要遵守该协议,然后实现代理方法,就可以收到消息,并做相应的的处理。如果在开始时就注入很多的名称,就需要区分处理
if (message.name == "AppModel" ) {
//...
}
——WKWebView特性及使用 @360doc 【Swift】
WKUserScript API
WKUserScript 对象表示可以注入到网页中的脚本。
Symbols
初始化脚本
-
-initWithSource:injectionTime:forMainFrameOnly:
返回可以添加到用户内容控制器中的初始化用户脚本。
检查脚本信息
-
source
脚本源代码
-
injectionTime
脚本应该被注入网页中的时间点。
-
forMainFrameOnly
布尔值,指示脚本是否仅应注入主框架(是)或所有框架(否)。
常量
-
WKUserScriptInjectionTime
将用户脚本注入网页的时间。
typedef enum WKUserScriptInjectionTime : NSInteger {
WKUserScriptInjectionTimeAtDocumentStart, // 在文档元素创建之后,但在加载任何其他内容之前注入脚本。
WKUserScriptInjectionTimeAtDocumentEnd //在文件完成加载后,但在其他子资源完成加载之前注入该脚本。
} WKUserScriptInjectionTime;
使用示例
WKUserScript
对象可以以 JavaScript 源码形式初始化,初始化时还可以传入时间是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到WKUserContentController
中,并且以WKWebViewConfiguration
属性传入到WKWebView
的初始化过程中。
- (void)viewDidLoad {
[super viewDidLoad];
// 使用 WKUserScript 注入JavaScript脚本
// 图片缩放的js代码
NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '张图');";
// 根据JS字符串初始化 WKUserScript 对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 根据生成的 WKUserScript 对象,初始化 WKWebViewConfiguration 对象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
// 初始化 WKWebView 对象
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
_webView.UIDelegate = self;
_webView.navigationDelegate = self;
[_webView loadHTMLString:@"![](http://upload-images.jianshu.io/upload_images/2648731-54ae5490e32407cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)" baseURL:nil];
[self.view addSubview:_webView];
}
使用示例:自适应文本字体大小
场景:加载的 HTML 网页没有对移动端设备屏幕做适配,导致加载后显示的字体太大或者太小,影响用户体验,通过注入 JavaScript 脚本的方式可以解决这个问题。
以下为代码示例:
- (WKWebViewConfiguration *)webViewConfiguration {
if (!_webViewConfiguration) {
_webViewConfiguration = [[WKWebViewConfiguration alloc] init];
// 自适应字体
NSString *javaScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *contentController = [[WKUserContentController alloc] init];
[contentController addUserScript:userScript];
_webViewConfiguration.userContentController = contentController;
}
return _webViewConfiguration;
}
使用 WKWebView
WKWebView 新特性
加载 web 页面
加载本地资源
-
- loadHTMLString:baseURL:
同步方式加载本地资源,数据可以来源于本地文件或者硬编码的HTML字符串
//设定主页文件的基本路径,通过一个HTML字符串加载主页数据
- (IBAction)loadHTMLString:(id)sender {
//主页文件名
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"Baidu001"
ofType:@"html"];
//主页文件的基本路径
NSURL *bundleUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
//将 index.html 文件的内容读取到 NSString 对象中
NSError *error = nil;
NSString *html = [[NSString alloc] initWithContentsOfFile:htmlPath
encoding:NSUTF8StringEncoding
error:&error];
//数据加载没有错误的情况下
if (error == nil) {
[self.webView loadHTMLString:html baseURL:bundleUrl];
}
}
-
- loadData:MIMEType:characterEncodingName:baseURL:
指定MIME类型、编码集和 NSDate 对象加载一个主页数据,并设定主页文件基本路径
//NSData是一种二进制的字节数组类型。它没有字符集的概念,但用它来装载webView的时候必须指定字符集。
- (IBAction)loadDATA:(id)sender {
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index"
ofType:@"html"];
NSData *htmlData = [[NSData alloc] initWithContentsOfFile:htmlPath];
NSURL *bundleUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
[self.webView loadData:htmlData
MIMEType:@"text/html"
textEncodingName:@"UTF-8"
baseURL:bundleUrl];
}
加载本地HTML:iOS9以上的系统可以使用
WKWebView loadFileURL:allowingReadAccessToURL:
,iOS9以下的版本没有这个方法,需要先将本地HTML文件的复制到tmp目录中,然后使用loadRequest:
方法来加载。如果在HTML中引入其他资源文件,例如js,css,image等必须一同复制到temp目录中——WKWebview开发笔记 @Qin's Blog
加载网络资源
// 创建 WKWebView 对象
CGRect rect = CGRectMake(0, 0, self.view.width, self.view.height);
WKWebView *webView = [[WKWebView alloc] initWithFrame:rect];
// 设置导航代理,监听网页加载进程
_webView.navigationDelegate = self;
// 将 webView 对象添加到视图
[self.view addSubview:webView];
// 根据URL发起网络请求
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"www.google.com"]];
[webView loadRequest:request];
创建可与 JavaScript 交互的 WKWebView
1. Objective-C 调用 JavaScript 方法
-
JS 方法:
function alertAction(message) { alert(message); }
-
Objective-C 原生调用
// OC 调用 JS 的方法 [self.mainWebView evaluateJavaScript:@"alertAction('OC调用JS方法时传入的参数')" completionHandler:^(id _Nullable item, NSError * _Nullable error) { NSLog(@"alert"); }];
2. JavaScript 调用 Objective-C 方法
参考:WKWebView的使用和各种坑的解决方法(OC+Swift)
- 第一步:
- (WKWebViewConfiguration *)webViewConfiguration {
if (!_webViewConfiguration) {
// 创建 WKWebViewConfiguration 对象
_webViewConfiguration = [[WKWebViewConfiguration alloc] init];
// 创建 WKUserContentController 对象,提供 JavaScript 向 webView 发送消息的方法
WKUserContentController *userContentColtroller = [[WKUserContentController alloc] init];
// 添加消息处理,注意:self指代的对象需要遵守 WKScriptMessageHandler 协议,结束时需要移除
[userContentColtroller addScriptMessageHandler:self name:@"close"];
// 将 userConttentController 设置到配置文件
_webViewConfiguration.userContentController = userContentColtroller;
}
return _webViewConfiguration;
}
ScriptMessageHandler 的 name 必须和 web 中的 JavaScript 方法名称一致:
window.webkit.messageHandlers.close.postMessage("message");
- 第二步:
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"%s",__FUNCTION__);
if ([message.name isEqualToString:@"close"]) {
NSLog(@"WKScriptMessage:%@",message.body);
}
}
- 第三步:
- (void)dealloc {
// 移除 ScriptMessageHandler
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"close"];
}
显示加载进度条
对于 WKWebView ,有三个属性支持 KVO,因此我们可以监听其值的变化,分别是:
loading
、title
、estimatedProgress
,对应功能表示为:是否正在加载中、页面的标题、页面内容加载进度(值为0.0~1.0)
刚开始用的方法:WKWebView添加进度条 ,参照着写完发现如果网速好的话,会出现进度条走不完就被隐藏的现象。
解决方法:添加延时animation动画,参考:iOS WKWebView添加网页加载进度条
代码如下:
//
// OSCViewController.m
// 2015-01-31-WKWebViewDemo
//
// Created by TangJR on 15/1/31.
// Copyright (c) 2015年 tangjr. All rights reserved.
//
#import "OSCViewController.h"
#import
@interface OSCViewController ()
@property (nonatomic, strong) NSURL *URL;
@property (nonatomic, strong) WKWebViewConfiguration *webViewConfiguration;
@property (nonatomic, strong) WKWebView *webView;
/** 1️⃣ 添加 progressView 属性*/
@property (nonatomic, strong) UIProgressView *progressView;
@end
@implementation OSCViewController
#pragma mark - Lifecycle
- (void)loadView {
[super loadView];
self.view = self.webView;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 3️⃣ 将 progressView 添加到父视图
[self.view addSubview:self.progressView];
// 4️⃣ 使用 KVO 注册观察者
// 监听 WKWebView 对象的 estimatedProgress 属性,就是当前网页加载的进度
[self.webView addObserver:self
forKeyPath:@"estimatedProgress"
options:NSKeyValueObservingOptionNew
context:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:self.URL];
[self.webView loadRequest:request];
}
- (void)dealloc {
// 6️⃣ 移除观察者
[self.view removeObserver:self forKeyPath:@"estimatedProgress"];
}
#pragma mark - Custom Accessors
- (NSURL *)URL {
if (!_URL) {
_URL = [NSURL URLWithString:@"http://www.oschina.net/"];
}
return _URL;
}
- (WKWebViewConfiguration *)webViewConfiguration {
if (!_webViewConfiguration) {
_webViewConfiguration = [[WKWebViewConfiguration alloc] init];
}
return _webViewConfiguration;
}
- (WKWebView *)webView {
if (!_webView) {
_webView = [[WKWebView alloc] initWithFrame:[[UIScreen mainScreen] bounds] configuration:self.webViewConfiguration];
// 设置导航代理,监听网页加载进程
_webView.navigationDelegate = self;
}
return _webView;
}
/**
2️⃣ 初始化progressView
@return 返回初始化后的进度条视图
*/
- (UIProgressView *)progressView {
if (!_progressView) {
CGRect sreenBounds = [[UIScreen mainScreen] bounds];
CGRect progressViewFrame = CGRectMake(0, 64, sreenBounds.size.width, 1);
_progressView = [[UIProgressView alloc] initWithFrame:progressViewFrame];
// 设置进度条色调
_progressView.tintColor = [UIColor blueColor];
// 设置进度条跟踪色调
_progressView.trackTintColor = [UIColor whiteColor];
}
return _progressView;
}
#pragma mark - KVO
// 5️⃣ 接收变更后的通知,计算 webView 的进度条
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
self.progressView.progress = self.webView.estimatedProgress;
if (self.progressView.progress == 1) {
/*
* 添加一个简单的动画,将 progressView 的 Height 变为1.5倍
* 动画时长0.25s,延时0.3s后开始动画
* 动画结束后将 progressView 隐藏
*/
__weak __typeof(self)weakSelf = self;
[UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseInOut animations:^{
weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
} completion:^(BOOL finished) {
weakSelf.progressView.hidden = YES;
}];
}
}else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
#pragma mark - WKNavigationDelegate
// 页面开始加载web内容时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
self.progressView.hidden = NO;
// 防止 progressView 被网页挡住
[self.view bringSubviewToFront:self.progressView];
}
// 当web内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
}
// 页面加载失败时调用 ( 【web视图加载内容时】发生错误)
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"Error:%@",error.localizedDescription);
self.progressView.hidden = YES;
}
// 【web视图导航过程中发生错误】时调用。
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"Error:%@",error.localizedDescription);
self.progressView.hidden = YES;
// 如果请求被取消
if (error.code == NSURLErrorCancelled) {
return;
}
}
// 当Web视图的Web内容进程终止时调用。
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
self.progressView.hidden = YES;
}
@end
监听webView的 title 属性并设置标题
- (void)viewDidLoad {
[super viewDidLoad];
// ------ 监听 WKWebView 对象的 title 属性
[self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
NSURLRequest *request = [NSURLRequest requestWithURL:self.URL];
[self.webView loadRequest:request];
}
- (void)dealloc {
// ------ 移除观察者
[self.view removeObserver:self forKeyPath:@"title"];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"title"]) {
self.title = change[@"new"];
}else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
URL 中文处理
// URL 中文处理
- (NSURL *)URL {
if (!_URL) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
_URL = [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)@"https://www.oschina.net/ios/home", (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL,kCFStringEncodingUTF8))];
#pragma clang diagnostic pop
}
return _URL;
}
导航栏右侧添加刷新按钮
#pragma mark - Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
// 刷新按钮
UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(rightBarButtonDidClicked:)];
self.navigationItem.rightBarButtonItem = rightBarButtonItem;
}
#pragma mark - IBActions
- (void)rightBarButtonDidClicked:(id)sender {
[self.webView reload];
}
禁用长按选中文字效果
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
// 禁用选中效果
[self.webView evaluateJavaScript:@"document.documentElement.style.webkitUserSelect='none'" completionHandler:nil];
[self.webView evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none'" completionHandler:nil];
}
其他
- 基于 UIWebView 的加载进度条开源框架:NJKWebViewProgress
加载HTML图片大小自适应
参考 WebView加载HTML图片大小自适应与文章自动换行
在 HTML 代码中设置内容样式,一般使用 css 或者 js ,根据加载优先级以及加载效果,可以自行选择。
- js在页面加载完之后加载,所以设置图片样式的时候,会先加载大图,然后突然变小;
- css在引入时加载,直接加载缩小的图片(实际占用内存不会缩小);
一:使用css进行图片的自适应
#define HTML_NO_HEAD @"![](http://upload-images.jianshu.io/upload_images/2648731-a7ddbb6f227eb44d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](http://upload-images.jianshu.io/upload_images/2648731-f4855a77d3daa14f.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
"
#define HTML_HAD_HEAD @"![](http://upload-images.jianshu.io/upload_images/2648731-a7ddbb6f227eb44d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](http://upload-images.jianshu.io/upload_images/2648731-f4855a77d3daa14f.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
"
- (void)viewDidLoad {
[super viewDidLoad];
// 没有标签
// [self.webView loadHTMLString:[self reSizeImageWithHTMLNoHead:HTML_NO_HEAD] baseURL:nil];
// 有标签
[self.webView loadHTMLString:[self reSizeImageWithHTMLHadHead:HTML_HAD_HEAD] baseURL:nil];
}
/ ----------------------------------------------------------------
// 1.图片样式:不管用户以前设置的图片尺寸是多大,都缩放到宽度为320px大小。
// 如果后台返回的HTML代码中,不包含 标签,则可以直接在HTML字符串前拼接代码。
- (NSString *)reSizeImageWithHTMLNoHead:(NSString *)html {
return [NSString stringWithFormat:@"%@", html];
}
// 如果包含 标签,则在标签内部替换添加
- (NSString *)reSizeImageWithHTMLHadHead:(NSString *)html {
return [HTML_HAD_HEAD stringByReplacingOccurrencesOfString:@"" withString:@""];
}
/ ----------------------------------------------------------------
// 2.图片样式:若需要根据图片原本大小,宽度小于320px的不缩放,大于320px的缩小到320px,那么在HTML字符串前加上一下代码:
- (NSString *)reSizeImageWithHTML:(NSString *)html {
return [NSString stringWithFormat:@"%@", html];
}
二:使用js进行图片的自适应
在webview的代理中,执行js代码。(下面这段代码是必须有
标签的)如果没有
标签,也很简单,只需要给返回的HTML字符串前面拼接一个即可。- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[webView stringByEvaluatingJavaScriptFromString:
@"var script = document.createElement('script');"
"script.type = 'text/javascript';"
"script.text = \"function ResizeImages() { "
"var myimg,oldwidth,oldheight;"
"var maxwidth=320;"// 图片宽度
"for(i=0;i maxwidth){"
"myimg.width = maxwidth;"
"}"
"}"
"}\";"
"document.getElementsByTagName('head')[0].appendChild(script);"];
[webView stringByEvaluatingJavaScriptFromString:@"ResizeImages();"];
}
文章内容自动换行
文章的自动换行也是通过css实现的,书写方式图片缩放类似。在没有
标签的情况下,在HTML代码前,直接拼接以下代码即可(若包含,则将代码添加到body标签内部),意思是全部内容自动换行。
参考
ShingoFukuyama/WKWebViewTips
浅谈 WKWebView 使用、JS 的交互
iOS 新闻类 App 内容页技术探索
WKWebView 在实际开发中的使用汇总 【URL中文处理、监听estimatedProgress、title、H5通过原生方法调用相册】
WKWebView @nshipster 【 Swift】
WKWebView的新特性与使用 @saitjr
使用safari对webview进行调试 @saitjr
WebView加载HTML图片大小自适应与文章自动换行 @saitjr
还在用UIWebView?何不试试WKWebView @吴白WKWebview开发笔记 @Qin's Blog 【清理缓存?】
WKWebView特性及使用 @360doc 【Swift】
带你走进WKWebView的世界 @SOI
WKWebView的使用和各种坑的解决方法(OC+Swift)
js(javascript)与ios(Objective-C)相互通信交互 @天狐博客
WKWebView 使用及注意点(keng) @伯乐在线
ShingoFukuyama /WKWebViewTips
IOS进阶之WKWebView @ 翻滚的牛宝宝
iOS8 WebKit库之——WKWebView篇iOS WebViewJavascriptBridge简单运用方法 @iSuAbner
iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge
WK 与 JS 的那些事
API翻译:UIWebView (Swift版)
API翻译:WebKit
-
API翻译:WKWebView (Swift版)