网页和原生代码的交互----WKWebView

目录

WKWebView环境中的交互操作

Web环境中注入JS代码

JS调用原生方法

原生调用JS方法

WKWebView与原生交互实现


之前分析了使用UIWebView与原生交互的实现方式,在iOS8.0之后apple建议开发者使用WKWebView来做web界面的加载展示,尤其是在iOS12.0之后已经开始废弃对UIWebView的更新支持,之所以apple开始推荐使用WKWebView的使用是因为WKWebView使用多进程处理web加载在性能上远远优于UIWebView.

WKWebView环境中的交互操作

在WKWebView中,原生可以通过三种方式完成与JS的交互:即

  1. Web环境中注入JS代码;
  2. JS调用原生方法;
  3. 原生调用JS方法.

Web环境中注入JS代码

WKWebView将JS调用原生的过程进一步的封装

  • 使用WKUserScript封装需要注入的JS方法;
  • /*! @abstract Returns an initialized user script that can be added to a @link WKUserContentController @/link.
     @param source The script source.
     @param injectionTime When the script should be injected.
     @param forMainFrameOnly Whether the script should be injected into all frames or just the main frame.
     */
    
    - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
  • 使用WKUserContentController添加WKUserScript对象:
  • - (void)addUserScript:(WKUserScript *)userScript;
  • 将WKUserContentController赋值给WKWebViewConfiguration对象;
  • /*! @abstract The user content controller to associate with the web view.
    */
    @property (nonatomic, strong) WKUserContentController *userContentController;
    
  • 使用WKWebViewConfiguration初始化WKWebView对象:
  • /*! @abstract Returns a web view initialized with a specified frame and
     configuration.
     @param frame The frame for the new web view.
     @param configuration The configuration for the new web view.
     @result An initialized web view, or nil if the object could not be
     initialized.
     @discussion This is a designated initializer. You can use
     @link -initWithFrame: @/link to initialize an instance with the default
     configuration. The initializer copies the specified configuration, so
     mutating the configuration after invoking the initializer has no effect
     on the web view.
     */
    - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

JS调用原生方法

在WKWebView中可以通过WKUserContentController注册供JS调用的方法:

  • 这里需要注意的是scriptMessageHandler会被WKUserContentController强引用,所以如果scriptMessageHandler本身对WKUserContentController进行了强引用就有可能导致循环引用从而造成内存泄漏(例如控制器强持有WKWebView,而控制器由实现了WKScriptMessageHandler协议成为scriptMessageHandler就会导致内存泄漏,此时可以通过在适当的时机移除scriptMessageHandler来循环引用).所以一般可以通过其他对象来实现WKScriptMessageHandler协议成为scriptMessageHandler.
/*! @abstract Adds a script message handler.
 @param scriptMessageHandler The message handler to add.
 @param name The name of the message handler.
 @discussion Adding a scriptMessageHandler adds a function
 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
 frames.
 */
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

然后在scriptMessageHandler中实现WKScriptMessageHandler协议方法,

@required

/*! @abstract Invoked when a script message is received from a webpage.
 @param userContentController The user content controller invoking the
 delegate method.
 @param message The script message received.
 */
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

即可以在JS发起调用时:

window.webkit.messageHandlers.`registerName`.postMessage{`parameters`}

监听到该方法的调用:

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"getMessage:%@", message.name);//name为注册的方法名
    NSLog(@"getMessage:%@", message.body);//body为方法调用的参数
}

这样JS调用的方法以及参数就可以被原生监听到,完成JS调用原生方法实现.不过需要注意的是在确定scriptMessageHandler不再使用时,需要通过显式移除.

/*! @abstract Removes a script message handler.
 @param name The name of the message handler to remove.
 */
- (void)removeScriptMessageHandlerForName:(NSString *)name;

原生调用JS方法

在WKWebView的实现中,使用

/* @abstract Evaluates the given JavaScript string.
 @param javaScriptString The JavaScript string to evaluate.
 @param completionHandler A block to invoke when script evaluation completes or fails.
 @discussion The completionHandler is passed the result of the script evaluation or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

来完成原生调用JS的方法实现,如果方法有返回值可以在completionHandler获取到方法返回值以及方法调用实现出现异常.

WKWebView与原生交互实现

以之前UIWebView中JS与原生的交互演示作为基础完成WKWebView中JS与原生进行交互展示.

  • 创建需要注入的js文件:主要用于完成JS调用原生方法时存储回调函数,用于接收原生反馈;
  • ;(function(w, doc){
      //防止重复添加
      if(w.Bridge && w.uuid){
      return;
      }
    
      var responseCallbacks = {}; //回调方法map
      //产生函数唯一标识
       var uuid=(function(){
          return function(){
            var timestamp = new Date().getTime()
            return  timestamp;
          }
      })();
    
      //JS调用原生方法
      function callNative(data, responseCallback) {
        data = data || {}
        try{
            var cid = 'cid' + uuid();
            if(responseCallback) {
                responseCallbacks[cid] = responseCallback;//保存回调
                data.callbackID = cid; //回调时使用callbackID取出回调函数
            }
            w.webkit.messageHandlers.callObjc.postMessage(data);
            }catch(e){
                if(typeof console !== 'undefined') {
                console.error('[JSBridge] EXCEPTION: ', e);
            }
        }
      }
      //原生调用JS方法返回消息给JS
      function invokeJSCallback (cid, removeAfterExecute, config) {
        if (!cid) {
          return;
        }
        var cb = responseCallbacks[cid];
        if (!cb) {
          return;
        }
    
        if (removeAfterExecute) {
          delete (responseCallbacks[cid]);
        }
        var data = config;
        if (data.callbackID) {
          delete data.callbackID;
        }
        cb.call(null, data);
      }
      //将对象绑定在window上
      w.Bridge = {
        callNative:callNative.bind(this),
        invokeJSCallback: invokeJSCallback.bind(this),
      };
    })(window, document);
    
  • 创建用于承载WKWebView的控制器,并创建WKWebView实例:
  • - (WKWebView *)webview {
        if (!_webview) {
            WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
            WKUserContentController *userContentController = [[WKUserContentController alloc] init];
            //加载需要注入的js
            NSError *error = nil;
            NSString *path = [[NSBundle mainBundle] pathForResource:@"bridge" ofType:@"js"];
            NSString *js = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
            NSAssert(!error, @"加载JS出现异常");
            
            WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true];
            [userContentController addUserScript:script];
    
    
            //注册JS方法:如果控制器本身强持有了WKWebView,而控制器本身实现了WKScriptMessageHandler协议成为scriptMessageHandler,则需要在适当的时候移除scriptMessageHandler否则会造成内存泄漏
            [userContentController addScriptMessageHandler:self name:method_function_name];
    
            configuration.userContentController = userContentController;
            _webview = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:(configuration)];
            
        }
        return _webview;
    }
    
  • 服从WKScriptMessageHandler协议并实现方法处理:
  • @interface WKViewController ()<WKScriptMessageHandler>
    @end
    
    
    
    static NSString * const method_function_name = @"callObjc";
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
        if ([message.name isEqualToString: method_function_name]) {
            NSDictionary *params = (NSDictionary *)message.body;
            NSString *api = params[@"api"];
            if ([api isEqualToString:@"show.alert"]) {
                NSString *callbackID = params[@"callbackID"];
                NSDictionary *data = params[@"data"];
                NSArray<NSString *> *buttons = data[@"buttons"];
                NSString *title = data[@"title"];
                NSString *msg = data[@"msg"];
                showAlertController(title, msg, buttons, ^(NSDictionary *params){
                    NSString *js = [NSString stringWithFormat:@"Bridge.invokeJSCallback(\"%@\", 'true', %@)", callbackID, params.json];
                    [self.webview evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                        NSLog(@"response=%@, error:%@", response, error);
                    }];
                });
            } else {
                //other type actions
            }
        }    
    }

 

  • 在适当的时候移除scriptMessageHandler防止内存泄漏.
  • - (void)viewDidDisappear:(BOOL)animated {
        [super viewDidDisappear:animated];
        [self.webview.configuration.userContentController removeScriptMessageHandlerForName:method_function_name];
    }
    

     

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本demo是WKWebView的基本使用和交互 ,实现了原生调用js的方法、js调用原生的方法、通过拦截进行交互的方法;修改内容 加入沙盒 / /加载沙盒 不带参数 // NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); // NSString * path = [paths objectAtIndex:0]; // path = [path stringByAppendingString:[NSString stringWithFormat:@"/app/html/index.html"]]; // NSURL *url = [NSURL URLWithString:[[NSString stringWithFormat:@"file://%@",path] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL:[NSURL fileURLWithPath:NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject]]; // [self.wkView loadFileURL:url allowingReadAccessToURL:[NSURL fileURLWithPath: [paths objectAtIndex:0]]]; // 带参数 /* NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString * path = [paths objectAtIndex:0]; path = [path stringByAppendingString:[NSString stringWithFormat:@"/app/html/index.html"]]; NSURL * url = [NSURL fileURLWithPath:path isDirectory:NO]; NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; [queryItemArray addObject:[NSURLQueryItem queryItemWithName:@"version" value:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]]; [urlComponents setQueryItems:queryItemArray]; [self.wkView loadFileURL:urlComponents.URL allowingReadAccessToURL:[NSURL fileURLWithPath: [paths objectAtIndex:0]]]; */

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值