没有比这里更全的了,看我就好了
WKWebView 是苹果在iOS 8中引入的新组件,相比于UIWebView,WKWebView 内存占用小,加载速度快,增加进度条属性。
面试官😃 :项目中是否有使用过WKWebView,简述下使用流程?
#import "ViewController.h"
#import <Webkit/Webkit.h>
//WKNavigationDelegate: 加载成功,失败,是否允许跳转等
//WKUIDelegate: alert,弹窗等
//WKScriptMessageHandler: JS->OC
@interface ViewController ()<WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>
@property (nonatomic, strong) WKWebView *webView;
//进度条
@property (nonatomic, strong) UIProgressView *progressView;
@end
@implementation ViewController
- (void)dealloc {
//记得移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"share"];
[self.webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[self.webView removeObserver:self forKeyPath:@"title"];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//加载连接
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
[self.webView loadRequest:request];
//添加检测网页加载进度的观察者
[self.webView addObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) options:0 context:nil];
//添加检测网页标题的观察者
[self.webView addObserver:self forKeyPath:@"title" options:0 context:nil];
}
- (WKWebView *)webView
{
if (!_webView) {
//设置网页的配置文件
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
//允许视频播放
configuration.allowsAirPlayForMediaPlayback = YES;
//允许在线播放
configuration.allowsInlineMediaPlayback = YES;
//允许可以与网页交互,选择视图
configuration.selectionGranularity = YES;
//web内容池处理
configuration.processPool = [[WKProcessPool alloc] init];
//自定义配置,一般用于JS调用OC(OC拦截URL中的数据自定义操作)
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
//在OC中添加一个scriptMessageHandler,添加处理消息,self指代的对象需要遵守WKScripteMessageHandler协议,结束时候需要移除
[userContentController addScriptMessageHandler:self name:@"share"];
//允许用户更爱网页的设置
configuration.userContentController = userContentController;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
//alert,弹窗等
webView.UIDelegate = self;
//加载成功,失败,是否允许跳转等
webView.navigationDelegate = self;
//允许右滑返回上个连接,左滑前进
webView.allowsBackForwardNavigationGestures = YES;
webView.customUserAgent = @"WKWebViewDemo/1.0.0";
_webView = webView;
}
return _webView;
}
- (UIProgressView *)progressView
{
if (!_progressView) {
UIProgressView *progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 1, [UIScreen mainScreen].bounds.size.width, 2)];
progressView.tintColor = [UIColor blueColor];
progressView.trackTintColor = [UIColor clearColor];
}
return _progressView;
}
- (void)callOC
{
NSLog(@"%s", __func__);
}
- (void)callJS
{
//执行一段JS并返回结果
[self.webView evaluateJavaScript:@"share" completionHandler:^(id _Nullable, NSError * _Nullable error) {
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self callJS];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqual:NSStringFromSelector(@selector(estimatedProgress))] && object == self.webView) {
self.progressView.progress = self.webView.estimatedProgress;
}else if ([keyPath isEqualToString:@"title"] && object == self.webView){
self.navigationItem.title = self.webView.title;
}else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark -- WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"share"]) {
NSLog(@"message.body=%@", message.body);
}
}
#pragma mark -- WKNavigationDelegate
//开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
}
//加载成功
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
}
//内容开始返回
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
}
//加载失败
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
}
//白屏时调用(网页内存过大,会出现白屏问题)
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
[self.webView reload];
}
#pragma mark -- WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
{
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler
{
}
@end
面试官😃 :描述加载本地文件的步骤?
NSString * path = [[NSBundle mainBundle] pathForResource:@"javascript" ofType:@"html" inDirectory:@"jsbridge"];
NSString *resurl = [path stringByRemovingPercentEncoding];
NSURL *fileUrl = [NSURL fileURLWithPath:resurl];
[self.webView loadFileURL:fileUrl allowingReadAccessToURL:fileUrl];
面试官😃 :项目中是否有使用过WKWebView,描述与jS的交互流程?
一:OC调用JS
//执行一段JS并返回结果,如果出错error则不为空 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler{}
二:JS调用OC
使用WKScriptMessageHandler,
1,动态注入JS方法
//在OC中添加一个scriptMessageHandler,添加处理消息,self指代的对象需要遵守WKScripteMessageHandler协议,结束时候需要移除 [userContentController addScriptMessageHandler:self name:@"share"];
2,当JS中调用share方法时,
window.webkit.messageHandlers.share.postMessage("xxxstring");
在OC中会收到
WKScriptMessageHandler
的回调#pragma mark -- WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"share"]) { NSLog(@"message.body=%@", message.body); } }
面试官😃:WebViewJavascriptBridge使用过吗?
#import "WebViewJavascriptBridge.h" @property (nonatomic, strong) WebViewJavascriptBridge *bridge; /** * 使用 WebViewJavascriptBridge 实现 OC 与 JS 交互 */ - (void)setUpWebViewJavascriptBridge { if (_bridge) { return; } // 设置能够进行桥接 [WebViewJavascriptBridge enableLogging]; // 初始化 WebViewJavascriptBridge 实例, 设置代理, 进行桥接 _bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView]; [_bridge setWebViewDelegate:self]; __weak __typeof(self)weakSelf = self; /** JS 调用 OC ,设置导航条 title @param data 后台 JS 页面传过来的参数 @param registerHandler 要注册的事件名称(这里我们为 locationAlertViewWithMessage) @param handler 回调 block 函数 当后台触发这个事件的时候会执行 block 里面的代码 */ [_bridge registerHandler:@"_app_setTitle" handler:^(id data, WVJBResponseCallback responseCallback) { if (data) { NSDictionary *dic = (NSDictionary *)data; weakSelf.title = dic[@"title"]; } // responseCallback 给后台 JS 的回复 responseCallback(@"OK,已收到标题信息!"); }]; /** * JS 调用 OC ,关闭当前 H5 控制器 */ [_bridge registerHandler:@"_app_closeWebView" handler:^(id data, WVJBResponseCallback responseCallback) { [weakSelf closeTheView]; responseCallback(@"OK,已关闭当前 WebView "); }]; /** * OC 调用 JS ,获取 OC 的值 */ [_bridge callHandler:@"_app_getToken" data:@"userToken"]; } - (void)dealloc { [_bridge removeHandler:@"_app_setTitle"]; [_bridge removeHandler:@"_app_closeWebView"]; [_bridge removeHandler:@"_app_getToken"]; }
html代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="http://code.jquery.com/jquery-1.7.2.min.js"></script> <title>Document</title> </head> <body> <div id="JSCallOC">js调用oc</div> <div id="textOne"></div> </body> <script> // 固定写法 函数名字可变 这个方法是必须的 <!-- 申明交互 --> function setupWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } else { document.addEventListener('WebViewJavascriptBridgeReady' , function() { callback(WebViewJavascriptBridge) }, false ); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0) } //这也是固定的,OC调JS,需要给OC调用的函数必须写在这个函数里面 setupWebViewJavascriptBridge(function(bridge) { //oc调js不带参数不带回调 bridge.registerHandler('OCCallJS',function() { alert("oc调js不带参数不带回调"); }) //oc调用js带参数不带回调 bridge.registerHandler('OCCallJSParameter',function(params) { alert("oc调js带参数不带回调"+params); }) //oc调js带参数带回调 bridge.registerHandler('OCCallJSParameterBlock',function (params,responseCallback) { alert("oc调js带参数带回调"); responseCallback("参数回调"); }) }) // js调用oc // 每个方法的特殊处理 function setTitle() { WebViewJavascriptBridge.callHandler('_app_setTitle', '这是一个nav标题', function (response) { // 移动端回传的数据 alert('移动端回传的数据:' + response); }); } </script> </html>
面试官😃:OC调用异步JS方法,并获取JS的返回值,怎么实现?
使用,WebViewJavascriptBridge
- (void)nativeCallJS:(NSString *)func para:(NSString *)para block:(void (^)(id))block{ //在主线程调用 __block BOOL end = NO; [self.bridge callHandler:func data:para responseCallback:^(id responseData) { NSLog ( @"from js: %@" , responseData ) ; block(responseData); end = YES; }]; while (!end) { //阻塞主线程 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }
面试官😃 :比如说JS调用OC,并需要异步回调结果怎么处理的?
//别忘了,在configuration中的userContentController中添加scriptMessageHandler //[controller addScriptMessageHandler:self name:@"share"]; //记得适当时候remove哦 //JS调用share方法时,则会调用下面的方法 #pragma mark - WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"share"]) { NSDictionary *shareData = message.body; NSLog(@"shareNew分享的数据为: %@", shareData); //模拟异步回调 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为模拟分享失败 //执行回调 [self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) { if (!error) { NSLog(@"模拟回调,分享失败"); } }]; }); } }
对应的JS代码
/** * 分享方法,并且会异步回调分享结果 * @param {对象类型} shareData 一个分享数据的对象,包含title,imgUrl,link以及一个回调function * @return {void} 无同步返回值 */ function shareNew(shareData) { //这是该方法的默认实现,上篇文章中有所提及 var title = shareData.title; var imgUrl = shareData.imgUrl; var link = shareData.link; var result = shareData.result; //do something //这里模拟异步操作 setTimeout(function() { //2s之后,回调true分享成功 result(true); }, 2000); //用于WKWebView,因为WKWebView并没有办法把js function传递过去,因此需要特殊处理一下 //把js function转换为字符串,oc端调用时 (<js function string>)(true); 即可 shareData.result = result.toString(); window.webkit.messageHandlers.shareNew.postMessage(shareData); } function test() { //清空分享结果 shareResult.innerHTML = ""; //调用时,应该 share({ title: "title", imgUrl: "http://img.dd.com/xxx.png", link: location.href, result: function(res) { //这里shareResult 等同于 document.getElementById("shareResult") shareResult.innerHTML = res ? "success" : "failure"; } }); }
关键点:1,我们可以采用异步回调的方式,将返回值返回给js
2,一般js的参数中包含function是为了异步回调,这里我们可以把js的function转换为字符串,再传递给OC
面试官😃 :你是否了解WKWebView的Cookie管理?