工作中有个需求,需要H5页面中的退出按钮需要调用OC的pop返回上个页面,所以就用到JS和OC的交互,本以为手拿把来没得问题,但是我还是高估自己了。下面就复盘一下问题吧:
一、简介
OC和JS交互立刻就想到了苹果原生的库JavaScriptCore,我们先在类中导入#import <JavaScriptCore/JavaScriptCore.h>,然后监听相应的事件。
二、问题
但是这里出现一个问题,JS中通过相应的对象来调用方法,而不是直接调用方法。这类就引申出第一个知识点:
js调用iOS分两种情况:
1、js里面直接调用方法
2、js里面通过对象调用方法
a:我们先来看下直接调用的方法:
1、由于在重定向等其它情况下回多次调动,所以用webView.isLoading让其加载完之后,我们再调用,这样就保证了之加载一次的情况。
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
if (webView.isLoading) {
return;
}
//iOS调用js
//首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//js调用iOS
//第一种情况
//其中test1就是js的方法名称,赋给是一个block 里面是iOS代码
//此方法最终将打印出所有接收到的参数,js参数是不固定的 我们测试一下就知道
context[@"test1"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
}
b、里面通过对象调用方法
好巧不巧我们后台就是通过对象调的方法window.playbackapp.exit(),而我采用的还是第一种方法,所以怎么也无法调用到方法。和前端也讨论了半天,最终还是改动iOS这边的处理,所以通过对象调用方法就被应用了。
b1:首先,我们创建一个类,继承自NSObject,然后导入JavaScriptCore库。
b2、创建一个遵守JSExport的协议,并让我们创建的类实现该协议。
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSObjectProtocol <JSExport>
//js中的退出方法,要和我们OC的方法一致,否则不执行哦
-(void)exit;
@end
//创建的类实现上面的协议
@interface CCPBInterface : NSObject<JSObjectProtocol>
@property(nonatomic,weak) id<JSObjectProtocol> delegate;
@end
b3、加入方法和实现
在.m文件中,实现我们的协议。
-(void)exit
{
//在webview的代理中是子线程,所以一定要在主线程中处理。给通知,退出回放页面。
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate exit];
});
}
可以把所有需要提供给js调用的方法都写在CCPBInterface这个类当中,通过代理的方式与需要执行这些操作的类进行连接,也就是说CCPBInterface这个类只负责提供方法,不负责实现,具体的操作可以在具体的添加有webView的控制器里实现
。
这里特别强调一点,不要在CCPBInterface这个类中执行方法,经过代码执行,通知是不执行的。我们去除掉主线程打印一下结果:
2019-06-11 19:46:04.681493+0800 CCClassRoom[12812:2824719] ~~~~~~~NSThread:<NSThread: 0x28153bdc0>{number = 2, name = (null)}
可以看出在webViewDidFinishLoad中执行的方法是在子线程中,而子线程是不可以更新UI的,虽然有时候也会更新成功,但是会有大量的警告打印。并且也会出现莫名其妙的崩溃,所以回到主线程时必须必要的。
b4、回到WebView中,我们调用代理,然后更新操作,此时是成功的。
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//1、禁止在重定向时多次加载,由于会调动一次,所以用i讲第一次绕过去。
if (webView.isLoading) {
return;
}
self.context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
CCPBInterface *ccpb=[CCPBInterface new];
ccpb.delegate =self;
//添加对象
self.context[@"playbackapp"]=ccpb;
// 此处是OC调用JS方法,如果使用它,那么不管你点击的是不是exit方法,每次调用这个方法。让你觉得好像没啥用。切记哦!
// NSString *jsStr1=@"playbackapp.exit()";
// [self.context evaluateScript:jsStr1];
}
b5、执行代理,发送出去通知,在Controller页面执行pop操作。
- (void)exit{
[[NSNotificationCenter defaultCenter] postNotificationName:@"goBackLoginView" object:nil];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:@"goBackLoginView"];
}
10月29号补充:
OC调用JS的方法,此处用的控件是WKWebView。
NSString *shareMethod = @"pauseTimers()";
[_webview evaluateJavaScript:shareMethod completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
NSLog(@"~~~~~~~~~~obj:%@~~~~error:%@",obj,error);
}];
网友评论