概述
本篇文章将阐述,WebKit核新的类和协议、MessageHandler消息处理器、JS->OC,OC->JS 交互流程、WKNavagationDelegate协议、WKUIDelegate协议等类或方法分析WebKit
内核的原理及应用说明。
重点注意
WebKit
的函数和方法只能在App
的主线程或者主队列中调用。
WebKit核新的类和协议
WKWebView:网页的渲染与展示,通过WKWebViewConfiguration可以进行配置。
WKWebViewConfiguration:这个类专门用来配置WKWebView。
WKPreference:这个类用来进行M相关设置。
WKProcessPool:这个类用来配置进程池,与网页视图的资源共享有关。
WKUserContentController:这个类主要用来做native与JavaScript的交互管理。
WKUserScript:用于进行JavaScript注入。
WKScriptMessageHandler:这个类专门用来处理JavaScript调用native的方法。
WKNavigationDelegate:网页跳转间的导航管理协议,这个协议可以监听网页的活动。
WKNavigationAction:网页某个活动的示例化对象。
WKUIDelegate:用于交互处理JavaScript中的一些弹出框。
WKBackForwardList:堆栈管理的网页列表。
WKBackForwardListItem:每个网页节点对象。
使用WKWebViewConfiguration对WebView进行配置
WebbView初始化
初始化函数
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration;
加载方式,建议使用苹果推荐使用的函数
// 加载HTML文件,推荐使用
- (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;
// 加载一个请求,推荐使用
- (WKNavigation *)loadRequest:(NSURLRequest *)request;
// 加载本地文件,如Html文件等,不推荐使用
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
// // 加载本地文件,如Html文件等,不推荐使用
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
WKWebViewConfiguration内容配置
-
WKPerference
偏好设置
//进行偏好设置
WKPreferences * preference = [[WKPreferences alloc]init];
//最小字体大小 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果
preference.minimumFontSize = 0;
//设置是否支持javaScript 默认是支持的
preference.javaScriptEnabled = YES;
//设置是否允许不经过用户交互由javaScript自动打开窗口
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;
-
WKUserContentController
:native
与JavaScript
的交互管理
WKUserContentController
最重要的对象方法就是addScriptMessageHandler:name:
,我把这个功能简称为MessageHandler
(消息处理器)。- addScriptMessageHandler:name:
有两个参数,第一个参数是userContentController
的代理对象(通常是self
),第二个参数是JS里发送postMessage
的对象(相当于一个native
与js
相互识别的一个标识)。使用MessageHandler
功能,就必须要实现WKScriptMessageHandler
协议。
//设置内容交互控制器 用于处理JavaScript与native交互
WKUserContentController * userController = [[WKUserContentController alloc]init];
//设置处理代理并且注册要被js调用的方法名称
[userController addScriptMessageHandler:self name:@"name"];
//js注入,注入一个测试方法。
NSString *javaScriptSource = @"function userFunc(){window.webkit.messageHandlers.name.postMessage( {\"name\":\"HS\"})}";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
// forMainFrameOnly:NO(全局窗口),yes(只限主窗口)[userController addUserScript:userScript];
config.userContentController = userController;
- 在native代理的回调方法中,会获取到JavaScript传递进来的消息
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
//这里可以获取到JavaScript传递进来的消息
}
-
WKScriptMessage
类是JavaScript
传递的对象实例
//传递的消息主体
@property (nonatomic, readonly, copy) id body;
//传递消息的WebView
@property (nullable, nonatomic, readonly, weak) WKWebView *webView;
//传递消息的WebView当前页面对象
@property (nonatomic, readonly, copy) WKFrameInfo *frameInfo;
//消息名称,也就是我们注册回调的“name”
@property (nonatomic, readonly, copy) NSString *name;
- 注入JS脚本
- (void)addUserScript:(WKUserScript *)userScript;
-
WKWebsiteDataStore
:WebKit
框架采用其本身的缓存框架
@interface WKWebsiteDataStore : NSObject
//获取默认的存储器 此存储器为持久性的会被写入磁盘
+ (WKWebsiteDataStore *)defaultDataStore;
//获取一个临时的存储器
+ (WKWebsiteDataStore *)nonPersistentDataStore;
//存储器是否是临时的
@property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
//所有可以存储的类型
+ (NSSet<NSString *> *)allWebsiteDataTypes;
@end
//设置数据存储store
config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
//设置是否将网页内容全部加载到内存后再渲染
config.suppressesIncrementalRendering = NO; //设置HTML5视频是否允许网页播放 设置为NO则会使用本地播放器
config.allowsInlineMediaPlayback = YES; //设置是否允许ariPlay播放
config.allowsAirPlayForMediaPlayback = YES; //设置视频是否需要用户手动播放 设置为NO则会允许自动播放
config.requiresUserActionForMediaPlayback = NO; //设置是否允许画中画技术 在特定设备上有效
config.allowsPictureInPictureMediaPlayback = YES; //设置选择模式 是按字符选择 还是按模块选择/*
typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
//按模块选择
WKSelectionGranularityDynamic,
//按字符选择
WKSelectionGranularityCharacter,
} NS_ENUM_AVAILABLE_IOS(8_0);
*/
config.selectionGranularity = WKSelectionGranularityCharacter; //设置请求的User-Agent信息中应用程序名称 iOS9后可用
config.applicationNameForUserAgent = @"HS";
JS->OC,OC->JS 交互流程
Native 实现方式
//防止频繁IO操作,造成性能影响
static NSString *nativejsSource;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
nativejsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"NativeApi" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
});
//添加自定义的脚本
WKUserScript *js = [[WKUserScript alloc] initWithSource:nativejsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[self.webView.configuration.userContentController addUserScript:js];
//注册分享回调
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeShare"];
// 注册添加联系人的回调
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeChoosePhoneContact"];
JS端实现方式
/**
* Native为H5提供的Api接口
*
* @type {js对象}
*/
var DANativeApi = (function() {
var NativeApi = {
/**
* 分享
* @param {js对象} shareInfo 分享信息和回调
* @return {void} 无同步返回值,异步返回分享结果 true or false
*/
share: function(shareInfo) {
if (shareInfo == undefined || shareInfo == null || typeof(shareInfo) !== "object") {
alert("参数" + JSON.stringify(shareInfo) + "不合法");
} else {
alert("分享的参数为" + JSON.stringify(shareInfo));
}
//调用native端
_nativeShare(shareInfo);
},
/**
* 从通讯录选择联系人
* @return {void} 无同步返回值,异步返回选择的结果
*/
choosePhoneContact: function(param) {
//具体是否需要判断
//调用native端
_nativeChoosePhoneContact(param);
}
}
//下面是一些私有函数
/**
* Native端实现,适用于WKWebView,UIWebView如何实现,小伙伴自己动脑筋吧~
* @param {js对象} shareInfo 分享的信息和回调
* @return {void} 无同步返回值,异步返回
*/
function _nativeShare(shareInfo) {
//用于WKWebView,因为WKWebView并没有办法把js function传递过去,因此需要特殊处理一下
//把js function转换为字符串,oc端调用时 (<js function string>)(true); 即可
//如果有回调函数,且为function
var callbackFunction = shareInfo.result;
if (callbackFunction != undefined && callbackFunction != null && typeof(callbackFunction) === "function") {
shareInfo.result = callbackFunction.toString();
}
//js -> oc
// 至于Android端,也可以,比如 window.jsInterface.nativeShare(JSON.stringify(shareInfo));
window.webkit.messageHandlers.nativeShare.postMessage(shareInfo);
}
/**
* Native端实现选择联系人,并异步返回结果
* @param {[type]} param [description]
* @return {[type]} [description]
*/
function _nativeChoosePhoneContact(param) {
var callbackFunction = param.completion;
if (callbackFunction != undefined && callbackFunction != null && typeof(callbackFunction) === "function") {
param.completion = callbackFunction.toString();
}
//js -> oc
window.webkit.messageHandlers.nativeChoosePhoneContact.postMessage(param);
}
//闭包,把Api对象返回
return NativeApi;
})();
/*
//调用时,分享
DANativeApi.share({
title: document.title,
desc: "",
url: location.href,
imgUrl: "",
result: function(res) {
// body...
alert("分享结果为:" + JSON.stringify(res));
}
});
//选择联系人
DANativeApi.choosePhoneContact({
completion: function(res) {
alert("选择联系人的结果为:" + JSON.stringify(res));
}
});
*/
释放回调
- (void)dealloc
{
// 注册的回调要及时释放,防止强引用,循环引用
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeShare"];
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeChoosePhoneContact"];
}
OC调用JS
// 将分享结果返回给js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
实现WKScriptMessageHandler协议方法(JS调用OC)
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// 分享
if ( [message.name isEqualToString:@"nativeShare"]) {
// JS ->OC
// 第一步,处理分享数据
NSDictionary *shareData = message.body;
NSLog(@"%@分享的数据为: %@", message.name, shareData);
// 第二步,弹出分享的图层,执行分享操作
// 第三部,模拟异步回调,将分享状态反馈给JS端
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//读取js function的字符串
NSString *jsFunctionString = shareData[@"result"];
//拼接调用该方法的js字符串
NSString *callbackJs = [NSString stringWithFormat:@"(%@)(%d);", jsFunctionString, NO]; //后面的参数NO为模拟分享失败
//执行回调OC->JS,调用JS端的方法,将分享状态告知js,然后evaluateJavaScript:completionHandler的回调处理JS对于分享状态反馈的回应。
[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
if (!error) {
NSLog(@"模拟回调,分享失败");
}
}];
});
}
//选择联系人
else if ([message.name isEqualToString:@"nativeChoosePhoneContact"]) {
[self selectContactCompletion:^(NSString *name, NSString *phone) {
NSLog(@"选择完成");
//读取js function的字符串
NSString *jsFunctionString = message.body[@"completion"];
//拼接调用该方法的js字符串
NSString *callbackJs = [NSString stringWithFormat:@"(%@)({name: '%@', mobile: '%@'});", jsFunctionString, name, phone];
//执行回调
[self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
}];
}];
}
}
WKWebView中的属性和方法解析
常用的属性和方法
//设置导航代理
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
//设置UI代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
//导航列表
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
//通过url加载网页视图
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
//通过文件加载网页视图
- (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;
//网页标题
@property (nullable, nonatomic, readonly, copy) NSString *title;
//网页的url
@property (nullable, nonatomic, readonly, copy) NSURL *URL;
//网页是否正在加载中
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
//加载进度 可以监听这个属性的值配合UIProgressView来设计进度条
@property (nonatomic, readonly) double estimatedProgress;
//是否全部是安全连接
@property (nonatomic, readonly) BOOL hasOnlySecureContent;
//证书列表
@property (nonatomic, readonly, copy) NSArray *certificateChain;
//是否可以回退
@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;
//是否允许右滑返回手势
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
WKBackForwardList类为导航管理的网页列表类
@interface WKBackForwardList : NSObject
//当前所在的网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *currentItem;
//前进的一个网页节
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *forwardItem;
//回退的一个网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *backItem;
//获取某个index的网页节点
- (nullable WKBackForwardListItem *)itemAtIndex:(NSInteger)index;
//获取回退的节点数组
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *backList;
//获取前进的节点数组
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *forwardList;
@end
WKBackForwardListItem 网页节点
@interface WKBackForwardListItem : NSObject
//当前节点的URL
@property (readonly, copy) NSURL *URL;
//当前节点的标题
@property (nullable, readonly, copy) NSString *title;
//创建此WebView的初始URL
@property (readonly, copy) NSURL *initialURL;
WKUserScript
//注入的js代码的脚本
WKUserScript *js = [[WKUserScript alloc] initWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[self.webView.configuration.userContentController addUserScript:js];
/*source为要注入的js代码
WKUserScriptInjectionTime设置注入的时机forMainFrameOnly参数设置是否只在主页面注入
typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
//原js代码运行前注入
WKUserScriptInjectionTimeAtDocumentStart,
//原js代码运行后注入
WKUserScriptInjectionTimeAtDocumentEnd
} NS_ENUM_AVAILABLE(10_10, 8_0);
*/
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
WKNavagationDelegate协议
该代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。
//追踪加载过程
// 页面开始加载时调用
-(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 didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到响应后,决定是否跳转
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
/*
在发送请求之前,决定是否跳转,决定是否响应网页的某个动作,例如加载,回退,前进,刷新等,在这个方法中,必须执行decisionHandler()代码块,并将是否允许这个活动执行在block中进行传入
*//*
WKNavigationAction是网页动作的抽象化,其中封装了许多行为信息,后面会介绍
WKNavigationActionPolicy为开发者回执,枚举如下:
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
//取消此次行为
WKNavigationActionPolicyCancel,
//允许此次行为
WKNavigationActionPolicyAllow,
} NS_ENUM_AVAILABLE(10_10, 8_0);
*/
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
decisionHandler(WKNavigationActionPolicyAllow);
}
WKUIDelegate协议
//创建新的webView时调用的方法
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{ return webView;
}
//关闭webView时调用的方法
-(void)webViewDidClose:(WKWebView *)webView{
}
//下面这些方法是交互JavaScript的方法
//JavaScript调用alert方法后回调的方法 message中为alert提示的信息 必须要在其中调用completionHandler()
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{ NSLog(@"%@",message);
completionHandler();
}
//JavaScript调用confirm方法后回调的方法 confirm是js中的确定框,需要在block中把用户选择的情况传递进去
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{ NSLog(@"%@",message);
completionHandler(YES);
}
//JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{ NSLog(@"%@",prompt);
completionHandler(@"123");
}
WKNavagationAction类
@interface WKNavigationAction : NSObject
//原页面
@property (nonatomic, readonly, copy) WKFrameInfo *sourceFrame;
//目标页面
@property (nullable, nonatomic, readonly, copy) WKFrameInfo *targetFrame;
//请求URL
@property (nonatomic, readonly, copy) NSURLRequest *request;
//活动类型/*typedef NS_ENUM(NSInteger, WKNavigationType) {
//链接激活 WKNavigationTypeLinkActivated,
//提交操作 WKNavigationTypeFormSubmitted,
//前进操作 WKNavigationTypeBackForward,
//刷新操作 WKNavigationTypeReload,
//重提交操作 例如前进 后退 刷新 WKNavigationTypeFormResubmitted,
//其他类型
WKNavigationTypeOther = -1,
} NS_ENUM_AVAILABLE(10_10, 8_0);
*/
@property (nonatomic, readonly) WKNavigationType navigationType;
@end
参考链接
https://www.cnblogs.com/XYQ-208910/p/5876419.html
https://www.jianshu.com/p/ab58df0bd1a1
https://www.jianshu.com/p/ab58df0bd1a1