WKWebView OC与JS交互

WKWebView OC与JS交互

整体上看原理还是挺简单的,即OC端向JS中注入JS脚本或者注入一个事件对象,生成一个时间handeler,当JS触发此事件时,handler处理事件的回调用,实现相互传值和事件交互的效果。

1. OC调用JS

OC调用JS方法主要是通过执行javaScript脚本来实现的。

WKWebKit提供的主要方法是:- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler

  • 例1:通过javaScript执行js里的某个函数
NSString *script = [NSString stringWithFormat:@"function('%@', '%@');", xx, xx];
[self.webView evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
    // ...
}];

通过的webView提供的API,执行一段javaScript脚本代码,即OC调用了js中的function()这个函数,xx,xx分别是调用js函数时候传递的两个参数。

  • 例2:通过evaluateJavaScript获取js某属性值

设置userAgent

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    // Set the user-Agent.
    [webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSString *versionIdentifier = [NSString stringWithFormat:@"%@ %@",WMS_CUSTOMER_USER_AGENT,[EIInfoPlist appVersion]];
        if (![result containsString:versionIdentifier]) {
            NSString *deviceIdentifier = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? @"iPad" :@"iPhone";
            webView.customUserAgent = [NSString stringWithFormat:@"%@^%@^%@",result,deviceIdentifier,versionIdentifier];
        }
        NSLog(@"user-Agent : %@",webView.customUserAgent);
    }];
}

如上,我们通过执行js的方法navigator.userAgent来获取userAgent,实际上是调用的navigator的getUserAgent()。

2. JS调用OC

JS调用OC方法,感觉更像是一种通知。

OC端需要先注册事件,这个事件可能是js端某个按钮的点击事件或者某个函数事件。

需要注入到JS中的消息handeler名称,在JS中应该会注册一个事件对象。

static NSString *const kLoadSuccessfully = @"kLoadSuccessfully";

通过以下方式注入js或者添加一个消息处理对象

    //  注册事件
    [self addScriptHandhelderTo:webView message:kSessionTimeOut javascript:nil];
    // 这种方式是注入一段js脚本
    //[self addScriptHandhelderTo:webView message:kSessionTimeOut javascript:[NSString stringWithFormat:@"function SessionTimeOut() { \n  window.webkit.messageHandlers.%@.postMessage(body); \n}", kSessionTimeOut]];
- (void)addScriptHandhelderTo:(WKWebView *)webView message:(NSString *)message javascript:(NSString *)javascript {
    
    if (message) {
        WMSJavascriptHandlerDelegateBridge *delegateBridge = [[WMSJavascriptHandlerDelegateBridge alloc] initWithDelegate:self];
        [webView.configuration.userContentController addScriptMessageHandler:delegateBridge name:message];
        [delegateBridge release];
    }
    if (javascript) {
        WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javascript
                                                          injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
                                                       forMainFrameOnly:YES];
        [webView.configuration.userContentController addUserScript:userScript];
        [userScript release];
    }
}

当JS触发该事件时,会通过window.webkit.messageHandlers.xx来获取到OC注入的事件对象xx,然后post一个message给OC端。

例如:

if (window.webkit && window.webkit.messageHandlers.NavigationBar) {
    window.webkit.messageHandlers.NavigationBar.postMessage({ ... });
}

NavigationBar就是OC端注入到JS中的一个事件对象。JS端通过 window.webkit.messageHandlers.NavigationBar这种方式,从messageHandlers里面获取到NavigationBar这个事件对象。然后通过 postMessage({ ... })发送一条消息到OC端,其中 {…} 内,可以自定义一些字段。这些字段对应的是OC端的message的body。NavigationBar对应的OC中的message的name。

再例如:

if (window.webkit && window.webkit.messageHandlers.LoadSuccessfully) {
    window.webkit.messageHandlers.LoadSuccessfully.postMessage({ Result: Sucess });
}

OC端需要实现WKScriptMessageHandler回调来接收JS发送的消息

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:kLoadSuccessfully]) {
        // ..
    }
}

message.name即JS端的事件对象,也就是OC端注册的事件对象,或者说是OC端注入到JS中的一个事件对象。
message.body即JS端发送的消息的消息体。

3. 总结

总之,JS和OC的通信,是通过OC向JS中注入事件对象或者注入JS脚本来实现的。

关键点:

  1. OC端必须注册事件/注入JS
  2. OC端需实现WKScriptMessageHandler来接收JS事件回调消息

注意:

  1. 添加了事件,需要在合适的地方移除事件,否则可能导致闪退

建议:
不建议每个事件都注册一次,建议一个WebView只注册一个事件,通过事件的消息体里自定义事件类型去做事件区分,这样的好处显而易见,注入少,代码也好管理,JS端调用也好处理。

下面直接上代码:

OC端:

#import "ViewController.h"
#import <WebKit/WebKit.h>

// 定义需要注册的js消息事件handler名称
static NSString *const kScriptMessageHandler = @"ScriptMessageHandler";


@interface ViewController ()<WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler> // 遵循 WKScriptMessageHandler 协议

@property (nonatomic, strong) WKWebView *webView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    self.webView.UIDelegate = self;
    self.webView.navigationDelegate = self;
    NSString *URLString = @"https://";
    NSURLRequest *testRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:URLString]];
    [self.webView loadRequest:testRequest];
    [self.view addSubview:self.webView];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // 注册js事件
    //这里给定一个消息事件的handler名称,在js端会对应的生成一个事件对象
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:kScriptMessageHandler];
}
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    // 移除注册的js事件
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:kScriptMessageHandler];
}

#pragma mark - WKScriptMessageHandler

// 实现 WKScriptMessageHandler 协议,接收js发送的时间回调
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    
    // 这里的message.name即我们注册过的 @"ScriptMessageHandler"
    // message.body是一个map,这个可以在js中自定义,传输任何key:value。
    // 建议js端在body中有一个固定的字段,例如EventType,用来区分事件。这样的话我们只注册一个ScriptMessageHandler,实现多个不同事件的处理。减少代码,方便管理,同时减少js上的内存开销。不管是对客户端还是js端来说都是一个更加合理的解决方案。
    NSLog(@"didReceiveScriptMessage %@ %@",message.name,message.body);
    if ([message.name isEqualToString:kScriptMessageHandler]) {
        NSString *eventType =message.body[@"EventType"];
        if ([eventType isEqualToString:@"EventType_ReLogin"]) {
            // ...
        }
        ...
    }
}

@end

js端

if (window.webkit && window.webkit.messageHandlers.ScriptMessageHandler) {
    window.webkit.messageHandlers.ScriptMessageHandler.postMessage({ Result: sucess, EventType: EventType_ReLogin
});

这是js里的一个Sesstion过期,通知OC端重新登录,等待OC端登的方法。使用window.webkit.messageHandlers.SessionTimeOut来发送消息给OC端。这里的SessionTimeOut是客户端你和js端商定好的,OC端注册到js的事件名称。
在这里插入图片描述

打印了一下js端的messageHandlers和window.webkit.messageHandlers.ScriptMessageHandler,都是对象类型的,这也说明了注册事件本质上就是生成了一个js事件对象。下截图中的SesstionTimeOut即这里的ScriptMessageHandler,调试时demo中随便写了一个名字,这个可忽略。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Morris_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值