IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> WKWebView 详解和一些问题处理 -> 正文阅读

[移动开发]WKWebView 详解和一些问题处理

WKWebView有14个类

WKBackForwardList:之前访问过的web页面的列表,可以通过后退和前进动作来访问到。
WKBackForwardListItem:webView中后退列表里的某一个网页。
WKFrameInfo:包含一个网页的布局信息
WKNavigation:包含一个网页的加载进度信息。
WKNavigationAction:包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。
WKNavigationResponse:包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。
WKPreferences:概括一个webiew的偏好设置。
WKProcessPool:表示一个web内容进程池。
WKUserContentController:提供使用JavaScript post信息和注册script的方法。
WKScriptMessage:包含网页发出的信息。
WKUserScript:表示可以被网页接受的用户脚本。
WKWebViewConfiguration:初始化webview的设置。
WKWindowFeatures:控制加载新网页时的窗口属性。

3个协议

WKNavigationDelegate:提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否进行页面加载新页面的相关方法。
WKUIDelegate:提供用原生控件显示网页的方法回调
WKScripMessageHandler:提供从网页中收消息的回调方法。

所有相关的类的API

//上文介绍过的偏好配置
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
// 导航代理 
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
// 用户交互代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;

// 页面前进、后退列表
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;

// 默认构造器
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;


//加载请求API
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;

// 加载URL
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0);

// 直接加载HTML
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

// 直接加载data
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);

// 前进或者后退到某一页面
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;

// 页面的标题,支持KVO的
@property (nullable, nonatomic, readonly, copy) NSString *title;

// 当前请求的URL,支持KVO的
@property (nullable, nonatomic, readonly, copy) NSURL *URL;

// 标识当前是否正在加载内容中,支持KVO的
@property (nonatomic, readonly, getter=isLoading) BOOL loading;

// 当前加载的进度,范围为[0, 1]
@property (nonatomic, readonly) double estimatedProgress;

// 标识页面中的所有资源是否通过安全加密连接来加载,支持KVO的
@property (nonatomic, readonly) BOOL hasOnlySecureContent;

// 当前导航的证书链,支持KVO
@property (nonatomic, readonly, copy) NSArray *certificateChain NS_AVAILABLE(10_11, 9_0);

// 是否可以招待goback操作,它是支持KVO的
@property (nonatomic, readonly) BOOL canGoBack;

// 是否可以执行gofarward操作,支持KVO
@property (nonatomic, readonly) BOOL canGoForward;

// 返回上一页面,如果不能返回,则什么也不干
- (nullable WKNavigation *)goBack;

// 进入下一页面,如果不能前进,则什么也不干
- (nullable WKNavigation *)goForward;

// 重新载入页面
- (nullable WKNavigation *)reload;

// 重新从原始URL载入
- (nullable WKNavigation *)reloadFromOrigin;

// 停止加载数据
- (void)stopLoading;

// 执行JS代码
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

// 标识是否支持左、右swipe手势是否可以前进、后退
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;

// 自定义user agent,如果没有则为nil
@property (nullable, nonatomic, copy) NSString *customUserAgent NS_AVAILABLE(10_11, 9_0);

// 在iOS上默认为NO,标识不允许链接预览
@property (nonatomic) BOOL allowsLinkPreview NS_AVAILABLE(10_11, 9_0);

#if TARGET_OS_IPHONE
/*! @abstract The scroll view associated with the web view.
 */
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
#endif

#if !TARGET_OS_IPHONE
// 标识是否支持放大手势,默认为NO
@property (nonatomic) BOOL allowsMagnification;

// 放大因子,默认为1
@property (nonatomic) CGFloat magnification;

// 根据设置的缩放因子来缩放页面,并居中显示结果在指定的点
- (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point;

使用

1.首先要引入WebKit库

#import <WebKit/WebKit.h>

2.两种初始化方法

// 默认初始化
- (instancetype)initWithFrame:(CGRect)frame;

// 根据对webview的相关配置,进行初始化
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

3.加载网页

最基础的方法和WebView一样

NSURL *URL= [NSURL URLWithString:@"www.baidu,com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];

一些其他的加载方法

//加载本地URL文件
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL;

//加载本地HTML字符串
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

//加载二进制数据
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSSTring *)MIMEType characterEncodingName:(NSString *)characterEndcodingName baseURL:(NSURL *)baseURL;

4.代理方法

1.【WKNavigationDelegate协议】

该代理提供的方法,可以用来跟踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。

// 页面开始加载时调用
- (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;
// 提交发生错误时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;

页面跳转的代理方法有三种,分为(收到挑转与决定是否跳转两种)

// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到相应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void(^)(WKNavigationResponsePolicy))decisonHandler;
// 在发送请求之前,决定是否跳转
  • 需要响应身份验证时调用 同样在block中需要传入用户身份凭证
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    //用户身份信息
    NSURLCredential * newCred = [[NSURLCredential alloc] initWithUser:@"user123" password:@"123" persistence:NSURLCredentialPersistenceNone];

    //为 challenge 的发送方提供 credential
    [challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
    completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}

【WKUIDelegate协议】

WKUIDelegate从名称能看出它时webView在user interface 上的代理,共有5个可选类型的代理方法。它为webView提供了原生的弹框,而不是JavaScript里的提示框。虽然JavaScript的提示框可以做的跟原生一样,但是对于ios开发者来说,如果要能更改提示框就更方便了,提供这个代理,可以让ios端更加灵活的修改提示框的样式。

// 新建WKWebView
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
// 关闭WKWebView
- (void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9.0);

// 对应js的Alert方法
/**
 *  web界面中有弹出警告框时调用
 *
 *  @param webView           实现该代理的webview
 *  @param message           警告框中的内容
 *  @param frame             主窗口
 *  @param completionHandler 警告框消失调用
 */
- (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方法(l例如输入框)
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;

【WKScriptMessageHandler】

这个协议中包含一个必须实现的方法,这个方法是native与web端交互的关键,它可以直接将接收到的JS脚本转为OC或Swift对象。

// 从web界面中接收到一个脚本时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

重点之WKWebView和JS交互

<html>
    <!--描述网页信息-->
    <head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>title</title>
 		<style>
            *{
                font-size: 50px;
            }
        
            .btn{height:80px; width:80%; padding: 0px 30px; background-color: #0071E7; border: solid 1px #0071E7; border-radius:5px; font-size: 1em; color: white}
 		</style>
        
        <script>
            
        
            //OC调用JS的方法列表
        	function alertMobile() {
                document.getElementById('mobile').innerHTML = '不带参数'
        	}

        	function alertName(msg) {
                //有一个参数
                document.getElementById('name').innerHTML = '有一个参数 :' + msg
        	}

        	function alertSendMsg(num,msg) {
        		//有两个参数
                document.getElementById('msg').innerHTML = '有两个参数:' + num + ',' + msg + '!!'
            }
        
            //JS响应方法列表
            function btnClick1() {
                window.webkit.messageHandlers.showMobile.postMessage(null)
            }

            function btnClick2() {
                window.webkit.messageHandlers.showName.postMessage('有一个参数')
            }

            function btnClick3() {
                window.webkit.messageHandlers.showSendMsg.postMessage(['两个参数One', '两个参数Two'])
            }

        </script>
        
        
    </head>

    <!--网页具体内容-->
    <body>
        <br/>

		<div>
			<label>WKWebView&JS交互</label>
		</div>
		<br/>

        <div id="mobile"></div>
		<div>
			<button class="btn" type="button" onclick="btnClick1()">不带参数</button>
		</div>
		<br/>
        
        <div id="name"></div>
		<div>
			<button class="btn" type="button" onclick="btnClick2()">一个参数</button>
		</div>
		<br/>
        
        <div id="msg"></div>
		<div>
			<button class="btn" type="button" onclick="btnClick3()">两个参数</button>
		</div>


    </body>
</html>

OC部分:

需要遵守的代理

1.设置偏好设置,以及JS调用OC添加处理脚本,这里的内容我写在了viewDidLoad里面,但是需要注意的是,需要在我们结束的时候释放WKUserController,不然会造成内存泄漏

- (void)viewDidLoad {
	[super viewDidLoad];
	// 设置偏好设置
	WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
	// 默认为0
	config.preference.miniumFontSize = 10;
	// 是否支持JavaScript
	config.preferences.javaScriptEnabled = YES;
	// 不通过用户交户,是否可以打开窗口
	config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
	
	self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 	   self.view.bounds.size.width, self.view.bounds.size.height/2) configuration:config];
	[self.view addSubview:self.webView];
	
	NSString *filePath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
	NSURL *baseURL = [[NSBundle mainBundle] bundleURL];
	[self.webView loadHTMLString:[NSString stringWithContentsOfFile:filePAth encoding:NSUTF8StringEncoding error:nil] baseURL:baseURL];
	WKUserContentController *userCC = config.userContentController;
	// JS 调用 OC 添加处理脚本
	[userCC addScriptMessageHandler:self name:@"showMobile"];
	[userCC addScriptMessageHandler:self name:@"showName"];
	[userCC addScriptMessageHandler:self name:@"showSendMsg"];
}

释放WKUserContentController代码

- (void)removeAllScriptMsgHandle {
	WKUserContentController *controller = self.web
}

在JS调用OC以后会走的代理

#pragma mark - WKScriptMessageHandler

// 直接将接收到的JS脚本转为OC或Swift对象。
- (void)userContentController:(WKUserContentController *)userController didReceiveScriptMessage:(WKScriptMessage *)message {
	NSLog(@"_cmd is %@", NSStringFromSelector(_cmd));
	NSLog(@"%@", message.body);
	 NSDictionary *dict = message.body;
        
   if (dict) {
      NSString *functionName = [dict objectForKey:@"functionName"];
   }
	
	 if ([message.name isEqualToString:@"showMobile"]) {
        [self showMsg:@"没有参数"];
    }
    
    if ([message.name isEqualToString:@"showName"]) {
        NSString *info = [NSString stringWithFormat:@"%@",message.body];
        [self showMsg:info];
    }
    
    if ([message.name isEqualToString:@"showSendMsg"]) {
        NSArray *array = message.body;
        NSString *info = [NSString stringWithFormat:@"有两个参数: %@, %@ !!",array.firstObject,array.lastObject];
        [self showMsg:info];
}

网页加载完成之后调用JS代码才会执行,因为这个时候html页面以及注入到webView中并且可以响应到对应的方法。OC调用JS代码

// 不带参数
- (IBAction)NOParameter:(id)sender {
		[self.webView evaluateJavaScript:@"alertMobile()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
			// JS 返回结果
			NSLog(@"%@ %@ ", response, error);
		}];
}

// 一个参数
- (IBAction)oneParameter:(id)sender {
	/*
		*alertName('奥特曼打小怪兽')
		*alertName JS方法名
		*奥特曼打小怪兽 带的参数
	*/
	[self.webView evaluateJavaScript:@"alertName('奥特曼打小怪兽')" completionHandler:nil];
}

//两个参数
- (IBAction)twoParameter:(id)sender {
	/*
		*alertSendMsg('我是参数1', '我是参数2')
		*alertSendMsg JS方法名
		*我是参数1 第一个参数
		*我是参数2 第二个参数
	*/
	[self.webView evaluateJavaScript:@"alertSendMsg('我是参数1', '我是参数2')" completionHandler:nil];
}

- (void)showMsg:(NSString *)msg {
	[[[UIAlertView alloc] initWithTitle:nil message:msg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
}

JS----->OC中URL拦截实现

// 针对一次action来决定是否允许跳转,允许与否都需要调用decisionHandler,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKwebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisonHanler {
	// 可以通过navigation.navigationType获取跳转类型,如新链接、后退等
	NSURL *URL = navigationAction.request.URL.URL;
	// 判断URL是否符合自定义的URL Scheme
	if ([URL.scheme isEqualToString:SHWebViewDemoScheme]) {
		// 根据不同的业务,来执行对应的操作,且获取参数
		if ([URL.host isEqualToString:SHWebViewDemoHostSmsLogin]) {
				NSString *param = URL.query;
				 NSLog(@"短信验证码登录, 参数为%@", param);//短信验证码登录, 参数为username=12323123&code=892845
				 decisionHandler(WKNavigationActionPolicyCancel);
            return;
		}
	}
	decisionHanler(WKNavigationActionPolicyAllow);
}

WKwebView相关的post请求实现

Html实现

<html>
    <head>
        <script>
            //调用格式: post('URL', {"key": "value"});
            function post(path, params) {
                var method = "post";
                var form = document.createElement("form");
                form.setAttribute("method",method);
                form.setAttribute("action",path);
                
                for(var key in params) {
                    if(params.hasOwnProperty(key)) {
                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type","hidden");
                        hiddenField.setAttribute("name",key);
                        hiddenField.setAttribute("value",params[key]);
                        
                        form.appendChild(hiddenField);
                    }
                }
                document.body.appendChild(form);
                form.submit();
            }
        </script>
    </head>
    <body>
    </body>
</html>

OC中代码实现:
思路:
1 、将一个包含JavaScript的POST请求的HTML代码放到工程目录中
2 、加载这个包含JavaScript的POST请求的代码到WKWebView
3 、加载完成之后,用Native调用JavaScript的POST方法并传入参数来完成请求

//仅当第一次的时候加载本地JS
// @property(nonatomic,assign) BOOL needLoadJSPOST;

- (void)viewDidLoad
{
     // JS发送POST的Flag,为真的时候会调用JS的POST方法
    self.needLoadJSPOST = YES;
    //POST使用预先加载本地JS方法的html实现,请确认WKJSPOST存在
    [self loadHostPathURL:@"WKJSPOST"];
}

- (void)loadHostPathURL:(NSString *)url
{
    //获取JS所在的路径
    NSString *path = [[NSBundle mainBundle] pathForResource:url ofType:@"html"];
    //获得html内容
    NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    //加载js
    [self.webView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];
}

//加载成功,对应UIWebView的- (void)webViewDidFinishLoad:(UIWebView *)webView; 网页加载完成,导航的变化
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    /*
     主意:这个方法是当网页的内容全部显示(网页内的所有图片必须都正常显示)的时候调用(不是出现的时候就调用),,否则不显示,或则部分显示时这个方法就不调用。
     */
    // 判断是否需要加载(仅在第一次加载)
    if (self.needLoadJSPOST) {
        // 调用使用JS发送POST请求的方法
        [self postRequestWithJS];
        // 将Flag置为NO(后面就不需要加载了)
        self.needLoadJSPOST = NO;
    }
}

#pragma mark - JSPOST
// 调用JS发送POST请求
- (void)postRequestWithJS
{
    // 拼装成调用JavaScript的字符串
    NSString *jscript = [NSString stringWithFormat:@"post('%@',{%@})",self.URLString,self.postData];
    NSLog(@"Javascript: %@", jscript);
    //post('http://www.postexample.com',{"username":"aaa","password":"123"})
    // 调用JS代码
    [self.webView evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) {
        NSLog(@"%@",error);
    }];
}

Cookie相关

比起UIWebView的自动管理,WKWebViewCookie管理坑还是比较多的,注意事项如下:
1、WKWebView加载网页得到的Cookie会同步到NSHTTPCookieStorage
2、WKWebView加载请求时,不会同步NSHTTPCookieStorage中已有的Cookie
3、通过共用一个WKProcessPool并不能解决2中Cookie同步问题,且可能会造成Cookie丢失。

添加cookie

动态注入js

WKUserContentController *UserContentController = [[WKUserContentController alloc] init];
 //添加自定义的cookie
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'SyhCookie=Syh;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
//添加脚本
 [UserContentController addUserScript:newCookieScript];

WKWebView执行js添加cookie.png

解决后续Ajax请求Cookie丢失问题

添加WKUserScript,需保证sharedHTTPCookieStorage中你的Cookie存在。

/*!
 *  更新webView的cookie
 */
- (void)updateWebViewCookie
{
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[self cookieString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    //添加Cookie
    [self.configuration.userContentController addUserScript:cookieScript];
}

- (NSString *)cookieString
{
    NSMutableString *script = [NSMutableString string];
    [script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
        // Skip cookies that will break our script
        if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
            continue;
        }
        // Create a line that appends this cookie to the web view's document's cookies
        [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.da_javascriptString];
    }
    return script;
}

@interface NSHTTPCookie (Utils)

- (NSString *)da_javascriptString;

@end

@implementation NSHTTPCookie (Utils)

- (NSString *)da_javascriptString
{
    NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                        self.name,
                        self.value,
                        self.domain,
                        self.path ?: @"/"];
    if (self.secure) {
        string = [string stringByAppendingString:@";secure=true"];
    }
    return string;
}

@end

解决跳转新页面时Cookie带不过去问题

当你点击页面上的某个链接,跳转到新的页面,Cookie又丢了,需保证sharedHTTPCookieStorage中你的Cookie存在。

//核心方法:
/**
 修复打开链接Cookie丢失问题

 @param request 请求
 @return 一个fixedRequest
 */
- (NSURLRequest *)fixRequest:(NSURLRequest *)request
{
    NSMutableURLRequest *fixedRequest;
    if ([request isKindOfClass:[NSMutableURLRequest class]]) {
        fixedRequest = (NSMutableURLRequest *)request;
    } else {
        fixedRequest = request.mutableCopy;
    }
    //防止Cookie丢失
    NSDictionary *dict = [NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies];
    if (dict.count) {
        NSMutableDictionary *mDict = request.allHTTPHeaderFields.mutableCopy;
        [mDict setValuesForKeysWithDictionary:dict];
        fixedRequest.allHTTPHeaderFields = mDict;
    }
    return fixedRequest;
}

#pragma mark - WKNavigationDelegate 

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

#warning important 这里很重要
    //解决Cookie丢失问题
    NSURLRequest *originalRequest = navigationAction.request;
    [self fixRequest:originalRequest];
    //如果originalRequest就是NSMutableURLRequest, originalRequest中已添加必要的Cookie,可以跳转
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
    //可能有小伙伴,会说如果originalRequest是NSURLRequest,不可变,那不就添加不了Cookie了,是的,我们不能因为这个问题,不允许跳转,也不能在不允许跳转之后用loadRequest加载fixedRequest,否则会出现死循环,具体的,小伙伴们可以用本地的html测试下。
    
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

#pragma mark - WKUIDelegate
// 页面是弹出窗口 _blank 处理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {

#warning important 这里也很重要
    //这里不打开新窗口
    [self.webView loadRequest:[self fixRequest:navigationAction.request]];
    return nil;
}

Cookie依然丢失

什么的方法需保证sharedHTTPCookieStorage中你的Cookie存在。怎么保证呢?由于**WKWebView加载网页得到的Cookie会同步到NSHTTPCookieStorage中**的特点,有时候你强行添加的Cookie会在同步过程中丢失。抓包(Mac推荐Charles)你就会发现,点击一个链接时,Requestheader中多了Set-Cookie字段,其实Cookie已经丢了。下面推荐笔者的解决方案,那就是把自己需要的Cookie主动保存起来,每次调用[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies方法时,保证返回的数组中有自己需要的Cookie。下面上代码,用了runtimeMethod Swizzling

首先是在适当的时候,保存

//比如登录成功,保存Cookie
NSArray *allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
for (NSHTTPCookie *cookie in allCookies) {
    if ([cookie.name isEqualToString:DAServerSessionCookieName]) {
        NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:DAUserDefaultsCookieStorageKey];
        if (dict) {
            NSHTTPCookie *localCookie = [NSHTTPCookie cookieWithProperties:dict];
            if (![cookie.value isEqual:localCookie.value]) {
                NSLog(@"本地Cookie有更新");
            }
        }
        [[NSUserDefaults standardUserDefaults] setObject:cookie.properties forKey:DAUserDefaultsCookieStorageKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
        break;
    }
}

在读取时,如果没有则添加

@implementation NSHTTPCookieStorage (Utils)

+ (void)load
{
    class_methodSwizzling(self, @selector(cookies), @selector(da_cookies));
}

- (NSArray<NSHTTPCookie *> *)da_cookies
{
    NSArray *cookies = [self da_cookies];
    BOOL isExist = NO;
    for (NSHTTPCookie *cookie in cookies) {
        if ([cookie.name isEqualToString:DAServerSessionCookieName]) {
            isExist = YES;
            break;
        }
    }
    if (!isExist) {
        //CookieStroage中添加
        NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:DAUserDefaultsCookieStorageKey];
        if (dict) {
            NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:dict];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
            NSMutableArray *mCookies = cookies.mutableCopy;
            [mCookies addObject:cookie];
            cookies = mCookies.copy;
        }
    }
    return cookies;
}

@end

Cookie 问题是目前 WKWebView 的一大短板

WKWebView Cookie存储

业界普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。

实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中,FireFox 工程师曾建议通过 reset WKProcessPool 来触发 Cookie 同步到 NSHTTPCookieStorage 中,实践发现不起作用,并可能会引发当前页面 session cookie 丢失等问题。

WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie

比如,NSHTTPCookieStorage 中存储了一个 Cookie:

name=Nicholas;value=test;domain=y.qq.com;expires=Sat, 02 May 2019 23:38:25 GMT;

通过 UIWebView 发起请求http://y.qq.com, 则请求头会自动带上 cookie: Nicholas=test;
而通过 WKWebView发起请求http://y.qq.com, 请求头不会自动带上 cookie: Nicholas=test。

WKProcessPool

苹果开发者文档对 WKProcessPool 的定义是:A WKProcessPool object represents a pool of Web Content process. 通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。

Workaround

由于许多 H5 业务都依赖于 Cookie 作登录态校验,而 WKWebView 上请求不会自动携带 Cookie, 目前的主要解决方案是:

a、WKWebView loadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题;
WKWebView * webView = [WKWebView new]; 
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://h5.qzone.qq.com/mqzone/index"]]; 

[request addValue:@"skey=skeyValue" forHTTPHeaderField:@"Cookie"]; 
[webView loadRequest:request];
b、通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题;
注意:document.cookie()无法跨域设置 cookie
WKUserContentController* userContentController = [WKUserContentController new]; 
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'skey=skeyValue';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; 

[userContentController addUserScript:cookieScript];

这种方案无法解决302请求的 Cookie 问题,比如,第一个请求是 www.a.com,我们通过在 request header 里带上 Cookie 解决该请求的 Cookie 问题,接着页面302跳转到 www.b.com,这个时候 www.b.com 这个请求就可能因为没有携带 cookie 而无法访问。当然,由于每一次页面跳转前都会调用回调函数:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

可以在该回调函数里拦截302请求,copy request,在 request header 中带上 cookie 并重新 loadRequest。不过这种方法依然解决不了页面 iframe 跨域请求的 Cookie 问题,毕竟-[WKWebView loadRequest:]只适合加载 mainFrame 请求。

WKWebView NSURLProtocol问题

WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView 上直接使用 NSURLProtocol 无法拦截请求。苹果开源的 webKit2 源码暴露了私有API:

+ [WKBrowsingContextController registerSchemeForCustomProtocol:]

通过注册 http(s) scheme 后 WKWebView 将可以使用 NSURLProtocol 拦截 http(s) 请求:

Class cls = NSClassFromString(@"WKBrowsingContextController”); 
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:"); 
if ([(id)cls respondsToSelector:sel]) { 
           // 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理 
           [(id)cls performSelector:sel withObject:@"http"]; 
           [(id)cls performSelector:sel withObject:@"https"]; 
}

但是这种方案目前存在两个严重缺陷:

a、post 请求 body 数据被清空

由于 WKWebView 在独立进程里执行网络请求。一旦注册 http(s) scheme 后,网络请求将从 Network Process 发送到 App Process,这样 NSURLProtocol 才能拦截网络请求。在 webkit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC 发送给 App Process。出于性能的原因,encode 的时候 HTTPBody 和 HTTPBodyStream 这两个字段被丢弃掉了

参考苹果源码:

https://github.com/WebKit/webkit/blob/fe39539b83d28751e86077b173abd5b7872ce3f9/Source/WebKit2/Shared/mac/WebCoreArgumentCodersMac.mm#L61-L88 (复制链接到浏览器中打开)

及bug report:

https://bugs.webkit.org/show_bug.cgi?id=138169 (复制链接到浏览器中打开)

因此,如果通过 registerSchemeForCustomProtocol 注册了 http(s) scheme, 那么由 WKWebView 发起的所有 http(s)请求都会通过 IPC 传给主进程 NSURLProtocol 处理,导致 post 请求 body 被清空

WKWebView自定义返回/关闭按钮

//返回按钮
@property (nonatomic)UIBarButtonItem* customBackBarItem;
//关闭按钮
@property (nonatomic)UIBarButtonItem* closeButtonItem;

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    [self updateNavigationItems];
    //允许跳转
    decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    [self updateNavigationItems];
}


- (void)updateNavigationItems
{
    if (self.webView.canGoBack) {
        UIBarButtonItem *spaceButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
        spaceButtonItem.width = -6.5;
        [self.navigationItem setLeftBarButtonItems:@[spaceButtonItem,self.customBackBarItem,self.closeButtonItem] animated:NO];
    }else {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        [self.navigationItem setLeftBarButtonItems:@[self.customBackBarItem]];
    }
}

-(UIBarButtonItem*)customBackBarItem{
    if (!_customBackBarItem) {
        UIImage* backItemImage = [[UIImage imageNamed:@"backItemImage"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        UIImage* backItemHlImage = [[UIImage imageNamed:@"backItemImage-hl"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
        
        UIButton* backButton = [[UIButton alloc] init];
        [backButton setTitle:@"返回" forState:UIControlStateNormal];
        [backButton setTitleColor:self.navigationController.navigationBar.tintColor forState:UIControlStateNormal];
        [backButton setTitleColor:[self.navigationController.navigationBar.tintColor colorWithAlphaComponent:0.5] forState:UIControlStateHighlighted];
        [backButton.titleLabel setFont:[UIFont systemFontOfSize:17]];
        [backButton setImage:backItemImage forState:UIControlStateNormal];
        [backButton setImage:backItemHlImage forState:UIControlStateHighlighted];
        [backButton sizeToFit];
        
        [backButton addTarget:self action:@selector(customBackItemClicked) forControlEvents:UIControlEventTouchUpInside];
        _customBackBarItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    }
    return _customBackBarItem;
}

-(void)customBackItemClicked{
    if (self.webView.goBack) {
        [self.webView goBack];
    }else{
        [self.navigationController popViewControllerAnimated:YES];
    }
}

-(UIBarButtonItem*)closeButtonItem{
    if (!_closeButtonItem) {
        _closeButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"关闭" style:UIBarButtonItemStylePlain target:self action:@selector(closeItemClicked)];
    }
    return _closeButtonItem;
}

-(void)closeItemClicked{
    [self.navigationController popViewControllerAnimated:YES];
}

WKWebView添加进度条

- (void)viewDidLoad {
	// 设置加载进度条
	//@property (nonatomic, strong) UIProgressView *progressView;
	//static void *WKwebBrowserContext = &WKwebBrowserContext;
	//添加进度条
	[self.view addSubview:self.progressView];
	[self.webView addObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) options:0 context:WkwebBrowserContext];
}

// 开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
	// 开始加载的时候,让加载进度条显示
	self.progressView.hiddden = NO;
}

#pragma mark - j进度条
- (void)obserValueForkeyPath:(NSStrig *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
	if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] && object == self.webView) {
		self.progressView.alpha = 1.0f;
		BOOL animated = self.webView.estimatedProgress > self.progressView.progress;
		[self.progressView setProgress:self.webView.estimatedProgress animated:animate];
		// Once complete,fade out UIProgressView
		if (self.webView.estimatedProgress >= 1.0f) {
			[UIView animateWithDuration:0.3f dealy:0.3f options:UIViewAnimationOptionCureEaseOut animations:^{
			 self.progressView.alpha = 0.0f;
            } completion:^(BOOL finished) {
                [self.progressView setProgress:0.0f animated:NO];
			}];
		}
	}
	else {
		[super obserValueForKeyPath:keyPath ofObject:object change:change context:context];
	}
}

- (UIProgressView *)progressView{
    if (!_progressView) {
        _progressView = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleDefault];
        if (_isNavHidden == YES) {
            _progressView.frame = CGRectMake(0, 20, self.view.bounds.size.width, 3);
        }else{
            _progressView.frame = CGRectMake(0, 64, self.view.bounds.size.width, 3);
        }
        // 设置进度条的色彩
        [_progressView setTrackTintColor:[UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0]];
        _progressView.progressTintColor = [UIColor greenColor];
    }
    return _progressView;
}

WKWebView填坑

js alert方法不弹窗

实现- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;方法,如果不实现,就什么都不发生,好吧,乖乖实现吧,实现了就能弹窗了。

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(nonnull void (^)(void))completionHandler
{
    //js 里面的alert实现,如果不实现,网页的alert函数无效
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    [self presentViewController:alertController animated:YES completion:^{}];
}
自定义contentInset刷新时页面跳动的bug
self.webView.scrollView.contentInset = UIEdgeInsetsMake(64, 0, 49, 0);
//史诗级神坑,为何如此写呢?参考https://opensource.apple.com/source/WebKit2/WebKit2-7600.1.4.11.10/ChangeLog  
[self.webView setValue:[NSValue valueWithUIEdgeInsets:self.webView.scrollView.contentInset] forKey:@"_obscuredInsets"]; //kvc给WKWebView的私有变量_obscuredInsets设置值

WKWebView loadRequest 问题

在 WKWebView 上通过 loadRequest 发起的 post 请求 body 数据会丢失:

//同样是由于进程间通信性能问题,HTTPBody字段被丢弃
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]];
[wkwebview loadRequest: request];

workaround:

假如想通过-[WKWebView loadRequest:]加载 post 请求 request1: http://h5.qzone.qq.com/mqzone/index,可以通过以下步骤实现:

  1. 替换请求 scheme,生成新的 post 请求 request2: post://h5.qzone.qq.com/mqzone/index, 同时将 request1 的 body 字段复制到 request2 的 header 中(WebKit 不会丢弃 header 字段);
  2. 通过-[WKWebView loadRequest:]加载新的 post 请求 request2;
  3. 通过 +[WKBrowsingContextController registerSchemeForCustomProtocol:]注册 scheme: post://;
  4. 注册 NSURLProtocol 拦截请求post://h5.qzone.qq.com/mqzone/index ,替换请求 scheme, 生成新的请求 request3: http://h5.qzone.qq.com/mqzone/index,将 request2 header的body 字段复制到 request3 的 body 中,并使用 NSURLConnection 加载 request3,最后通过 NSURLProtocolClient 将加载结果返回 WKWebView;

WKWebView 页面样式问题

在 WKWebView 适配过程中,我们发现部分H5页面元素位置向下偏移被拉伸变形,追踪后发现主要是H5页面高度值异常导致:

a. 空间H5页面有透明导航、透明导航下拉刷新、全屏等需求,因此之前 webView 整个是从(0, 0)开始布局,通过调整webView.scrollView.contentInset 来适配特殊导航栏需求。而在 WKWebView 上对 contentInset 的调整会反馈到webView.scrollView.contentSize.height的变化上,比如设置 webView.scrollView.contentInset.top = a,那么contentSize.height的值会增加a,导致H5页面长度增加,页面元素位置向下偏移;

解决方案是:调整WKWebView布局方式,避免调整webView.scrollView.contentInset。实际上,即便在 UIWebView 上也不建议直接调整webView.scrollView.contentInset的值,这确实会带来一些奇怪的问题。如果某些特殊情况下非得调整 contentInset 不可的话,可以通过下面方式让H5页面恢复正常显示:

/**设置contentInset值后通过调整webView.frame让页面恢复正常显示 
 *参考:http://km.oa.com/articles/show/277372
 */ 
webView.scrollView.contentInset = UIEdgeInsetsMake(a, 0, 0, 0); 
webView.frame = CGRectMake(webView.frame.origin.x, webView.frame.origin.y, webView.frame.size.width, webView.frame.size.height - a);

b. 在接入 now 直播的时候,我们发现在 iOS 9 上 WKWebView 会出现页面被拉伸变形的情况,最后发现是window.innerHeight值不准确导致(在WKWebView上返回了一个非常大的值),而H5同学通过获取window.innerHeight来设置页面高度,导致页面整体被拉伸。通过查阅相关资料发现,这个bug只在 iOS 9 的几个系统版本上出现,苹果后来fix了这个bug。我们最后的解决方案是:*延迟调用window.innerHeight*

setTimeout(function(){height = window.innerHeight},0);

or

Use shrink-to-fit meta-tag 
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1, shrink-to-fit=no">

视频自动播放

WKWebView 需要通过WKWebViewConfiguration.mediaPlaybackRequiresUserAction设置是否允许自动播放,但一定要在 WKWebView 初始化之前设置,在 WKWebView 初始化之后设置无效。

goBack API问题

WKWebView 上调用 -[WKWebView goBack], 回退到上一个页面后不会触发window.onload()函数、不会执行JS。

页面滚动速率

WKWebView 需要通过scrollView delegate调整滚动速率:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
     scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}

一些实用的方法:

添加观察者

//添加 监测网页加载进度 的观察者
    [self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                      options:0
                      context:nil];

//添加 监测网页标题title 的观察者
    [self.webView addObserver:self
                   forKeyPath:@"title"
                      options:NSKeyValueObservingOptionNew
                      context:nil];

监听方法

//---kvo 监听进度 必须实现此方法
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context
{
    if ([keyPath isEqualToString:@"estimatedProgress"])
    {
       self.progressView.progress = self.webView.estimatedProgress;
       if (self.progressView.progress == 1)
       {
           WeakSelfDeclare
           [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^
           {
               weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
           }
                            completion:^(BOOL finished)
           {
               weakSelf.progressView.hidden = YES;
           }];
       }
   }else if([keyPath isEqualToString:@"title"]
             && object == _webView){
        self.navigationItem.title = _webView.title;
    }else{
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

移除观察者

    [_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
    [_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(title))];

清除WK缓存

- (void)cleanCacheAndCookie
{
    //清除cookies
    NSHTTPCookie *cookie;
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (cookie in [storage cookies])
    {
        [storage deleteCookie:cookie];
    }
    
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
    NSURLCache * cache = [NSURLCache sharedURLCache];
    [cache removeAllCachedResponses];
    [cache setDiskCapacity:0];
    [cache setMemoryCapacity:0];
    
    WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
    [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                     completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records)
     {
         for (WKWebsiteDataRecord *record  in records)
         {
             
             [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                       forDataRecords:@[record]
                                                    completionHandler:^
              {
                  NSLog(@"Cookies for %@ deleted successfully",record.displayName);
              }];
         }
     }];
}

- (void)dealloc
{
    [_webView stopLoading];
    [_webView setNavigationDelegate:nil];
    [self clearCache];
    [self cleanCacheAndCookie];
}

点击链接无反应

#pragma mark WKNavigationDelegate

-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
    
}

WKWebView计算内容高度

添加KVO

[_webView.scrollView addObserver:selfforKeyPath:@"contentSize"options:NSKeyValueObservingOptionNewcontext:nil];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if ([keyPath isEqualToString:@"contentSize"]) {
        dispatch_async(dispatch_get_global_queue(0,0), ^{

            //document.documentElement.scrollHeight
            //document.body.offsetHeight
            [_webView evaluateJavaScript:@"document.documentElement.offsetHeight"completionHandler:^(id_Nullable result, NSError * _Nullable error) {

                CGRect frame =_webView.frame;
                frame.size.height = [result doubleValue] + 50;
                _webView.frame = frame;
                _scrollViewHeight =220 + _webView.height;
                _scrollView.contentSize =CGSizeMake(kScreenWidth,_scrollViewHeight);
            }];
        });
    }
}

H5调用了拨打电话功能

先拦截特点scheme,然后执行拨打电话的代码

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 拦截
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"tel"]) {
        NSString *resourceSpecifier = [URL resourceSpecifier];
        NSString *callPhone = [NSString stringWithFormat:@"telprompt://%@", resourceSpecifier];
        
        decisionHandler(WKNavigationActionPolicyCancel);
        // 拨打
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone]];
        return ;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:35:55  更:2021-12-24 18:36:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:36:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码