WKWebView从入门到放弃 (一 入门)
WKWebView 初始化
第一步:导入头文件 声明webview属性
// 导入头文件
#import <WebKit/WebKit.h>
// 声明webView属性
@property (nonatomic, strong) WKWebView *webView;
第二步 : 初始化webView
webview初始化位置**【尽量提前】**,如果过项目需要,甚至可以在APP启动后的某个特殊时机,预初始化一个全局webView。这样能够提前让项目初始化web内核,以减少初次进入webView的等待时间。 牺牲一定的性能,换取web启动时间,根据需求决定,我这里是放到了ViewController的ViewDidload方法中了。
// 使用webconfig方式初始化webView
_webView = [[WKWebView alloc] initWithFrame:frame configuration:self.webConfig];
// UI代理 主要是 alert 等弹窗的代理;
// 这里你可以设置一个专门的NSObject类遵守WKUIDelegate协议来处理回调方法 设置: _webView.UIDelegate = self.uiDelegater;
_webView.UIDelegate = self;
// 导航代理 同理 可设置一个代理类处理 导航跳转问题 继承 WKNavigationDelegate 协议
_webView.navigationDelegate = self;
// 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
_webView.allowsBackForwardNavigationGestures = YES;
_webView.backgroundColor = [UIColor clearColor];
// scrollView 滚动速度 UIScrollViewDecelerationRateFast:快速 UIScrollViewDecelerationRateNormal:正常速度
_webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
//_webView.customUserAgent = @"userAgent";// 自定义请求 userAgent
//_webView.allowsLinkPreview = YES;// 默认 不支持按压预览
//添加监测网页加载进度的观察者
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:@"LXWKWebViewController"];
//添加监测网页标题title的观察者
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
另外还有初始化方法:
// 通过制定URL初始化webView 太过固定
- (instancetype)initWithURL:(NSURL *)URL;
// 可定制webView的 网络策略 和 超时时间 缺点同上
- (instancetype)initWithURL:(NSURL *)URL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval NS_DESIGNATED_INITIALIZER;
第三步:加载网络请求
加载网页地址,如果需要记得在info.plist文件中提前开启ATS, NSAllowsArbitraryLoads --> YES,允许http的网页请求。
webRequest请求时机
某些机型中 loadRequest 方法 放在viewDidload方法中去执行,会出现webView白屏现象,所以我这边放到了viewwillappear方法中。这里要记得判断是否是初次viewwillappear,避免网页频繁刷新。
if (self.url && !_request){
//忽略本地缓存
_request = [NSURLRequest requestWithURL:self.url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
[self.webView loadRequest:_request];
}
webRequest请求参数
- 需要注意的属性时
NSURLRequestCachePolicy
:
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,// 默认 启用网页缓存,APP重启后会清空缓存
NSURLRequestReloadIgnoringLocalCacheData = 1,// 忽略本地缓存,每次都用最新的网络数据
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,// 不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,// 同上
NSURLRequestReturnCacheDataElseLoad = 2,// 无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据
NSURLRequestReturnCacheDataDontLoad = 3,// 无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
NSURLRequestReloadRevalidatingCacheData = 5,// 从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
};
- 超时时间不需要多说 一般最多 15-30s为宜
网页偏好设置:WKWebViewConfiguration
上代码 Getter Function
- (WKWebViewConfiguration *)webConfig{
if (!_webConfig) {
_webConfig = [[WKWebViewConfiguration alloc]init];
_webConfig.preferences.javaScriptEnabled = YES;//打开js交互
// 是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放
_webConfig.allowsInlineMediaPlayback = YES;
//设置视频是否需要用户手动播放 设置为NO则会允许自动播放
_webConfig.requiresUserActionForMediaPlayback = YES;
//设置是否允许画中画技术 在特定设备上有效
_webConfig.allowsPictureInPictureMediaPlayback = YES;
//设置请求的User-Agent信息中应用程序名称 iOS9后可用
_webConfig.applicationNameForUserAgent = @"ChinaDailyForiPad";
// 设置 preferences 主要是 网页浏览属性 支持js 交互什么的
_webConfig.preferences = self.webPreference;
// OC JS交互的方法
_webConfig.userContentController = self.webUserContentController;
// 识别h5中的邮箱电话识别
if (@available(iOS 10.0, *)) {
_webConfig.dataDetectorTypes = UIDataDetectorTypeAll;
} else {
// Fallback on earlier versions
}
}
return _webConfig;
}
- (WKWebViewConfiguration *)webConfig{
if (!_webConfig) {
_webConfig = [[WKWebViewConfiguration alloc]init];
// 是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放
_webConfig.allowsInlineMediaPlayback = YES;
//设置视频是否需要用户手动播放 设置为NO则会允许自动播放
_webConfig.requiresUserActionForMediaPlayback = YES;
//设置是否允许画中画技术 在特定设备上有效
_webConfig.allowsPictureInPictureMediaPlayback = YES;
//设置请求的User-Agent信息中应用程序名称 iOS9后可用
_webConfig.applicationNameForUserAgent = @"ChinaDailyForiPad";
// 设置 preferences 主要是 网页浏览属性 支持js 交互什么的
_webConfig.preferences = self.webPreference;
// OC JS交互的方法
_webConfig.userContentController = self.webUserContentController;
// 识别h5中的邮箱电话识别
if (@available(iOS 10.0, *)) {
_webConfig.dataDetectorTypes = UIDataDetectorTypeAll;
} else {
// Fallback on earlier versions
}
}
return _webConfig;
}
- (WKPreferences *)webPreference{
if (!_webPreference) {
// 创建设置对象
_webPreference = [[WKPreferences alloc]init];
//最小字体大小 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果
_webPreference.minimumFontSize = 10;
//设置是否支持javaScript 默认是支持的
_webPreference.javaScriptEnabled = YES;
// 在iOS上默认为NO,表示是否允许不经过用户交互由javaScript自动打开窗口
_webPreference.javaScriptCanOpenWindowsAutomatically = YES;
if (@available(iOS 13.0, *)) {// 恶意软件警告
_webPreference.fraudulentWebsiteWarningEnabled = YES;
}
}
return _webPreference;
}
- (WKUserContentController *)webUserContentController{
if (!_webUserContentController) {
_webUserContentController = [[WKUserContentController alloc] init];
}
return _webUserContentController;
}
WKWebViewConfiguration 结构
首先看一下这个类的结构
WKWebViewConfiguration
:这个类专门用来配置WKWebViewWKPreference
:这个类用来进行相关webView【偏好设置】WKProcessPool
:用来配置进程池,与网页视图的资源共享有关WKUserContentController
:主要用来做native与JavaScript的交互管理WKUserScript
:用于进行JavaScript注入WKScriptMessageHandler
:用来处理JavaScript回调native的方法
WKNavigationDelegate
网页状态回调
#pragma mark - **************** WKNavigationDelegate
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {}
//提交发生错误时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {}
// 接收到服务器跳转请求即服务重定向时之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {}
WKNavigationAction 网页活动回调
// 根据WebView对于即将跳转的HTTP请求头信息和相关信息来决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSString *urlStr = navigationAction.request.URL.absoluteString;
urlStr = [urlStr stringByRemovingPercentEncoding];
// 处理 H5 重定向问题
NSString *supportDomain = @"www.***.com";
WKFrameInfo *frameInfo = navigationAction.targetFrame;
if (![frameInfo isMainFrame] && [urlStr containsString:supportDomain]) {
[webView loadRequest:navigationAction.request];
self.url = navigationAction.request.URL;
}
// 自定义规则 拦截内部跳转
if ([urlStr hasPrefix:schemeHeader]) {
[ControllerUtils showViewWithURL:urlStr];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
// 微信跳转
if ([urlStr hasPrefix:@"weixin://"]) {
if ([[UIApplication sharedApplication] canOpenURL:navigationAction.request.URL]) {
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
NSLog(@"发送跳转请求:%@",urlStr);
// 允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
}
// 根据客户端受到的服务器响应头以及response相关信息来决定是否可以跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSString * urlStr = navigationResponse.response.URL.absoluteString;
urlStr=[urlStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];//个人情况,url里面会加入中文
NSLog(@"当前跳转地址:%@",urlStr);
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
/// 需要响应身份验证时调用 同样在block中需要传入用户身份凭证 ios 10.3 下 崩溃
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
// 判断服务器采用的验证方法
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
// 如果没有错误的情况下 创建一个凭证,并使用证书
if (challenge.previousFailureCount == 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}else {
// 验证失败,取消本次验证
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
//进程被终止时调用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
// [webView reload];
}
WKUIDelegate UI代理
#pragma mark - **************** WKUIDelegate
/**
* web界面中有弹出警告框时调用
*
* @param webView 实现该代理的webview
* @param message 警告框中的内容
* @param completionHandler 警告框消失调用
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
// 为避免 scheme 跳转界面后 alert弹出弹窗 崩溃问题,做一个兼容(VC A,模态跳转到B后,不能再由A模态跳转B了)
if (![[self appKeyWindowTopVC] isKindOfClass:[self.vcDelegate class]]) {
completionHandler();
return;
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self.vcDelegate presentViewController:alertController animated:YES completion:nil];
}
// ------------- 确认框
//JavaScript调用confirm方法后回调的方法 confirm是js中的确定框,需要在block中把用户选择的情况传递进去
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
// 同上
if (![[self appKeyWindowTopVC] isKindOfClass:[self.vcDelegate class]]) {
completionHandler(NO);
return;
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self.vcDelegate presentViewController:alertController animated:YES completion:nil];
}
// ------- 输入框
//JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
// 同上
if (![[self appKeyWindowTopVC] isKindOfClass:[self.vcDelegate class]]) {
completionHandler(@"");
return;
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self.vcDelegate presentViewController:alertController animated:YES completion:nil];
}
// 页面弹出窗口 _blank 处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
#pragma mark - **************** tool
- (UIViewController *)appKeyWindowTopVC{
UITabBarController *rootVC = (UITabBarController*)[(AppDelegate *)[UIApplication sharedApplication].delegate window].rootViewController;
UIViewController *presentedVC = rootVC;
while (presentedVC.presentedViewController) {
presentedVC = presentedVC.presentedViewController;
}
return presentedVC;
}
到这里,WKWebView基本上就可以使用了。