iOS WKWebView与JS交互面试题

没有比这里更全的了,看我就好了

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管理?

资源下载:WKWebViewDemo-IOS文档类资源-CSDN下载WKWebViewDemo更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/weixin_38934440/85839731

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值