因为苹果的文件机制,所有的资源文件都相当于放在bundle的路径里,里面不分任何文件夹路径,所以我们在加载(js, css, png)等等的资源文件的时候,不应该加上任何文件名,所以最好是把所有有关html的文件都放在同一平级的文件夹
UIWebView
1.OC调JS
/**
* ocCalls:js的函数名
*/
JSValue *value = self.jsContext[@"ocCalls"];
/**
* @[@"参数"]:传给js端的参数
*/
[value callWithArguments:@[@"参数"]];
2.JS调OC
JS调OC有好几种方法,这里我就只列举一种我个人常用的方法,这个可以写在加载之前就行
// 获取js上下文
self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 获取js对象,这样会强持有。。没释放对象,目前没解决(个人觉得用单例,这样起码不会一直创建对象,有哪个大神这个可以指教一下嘛0.0)
self.jsContext[@"obj"] = self.uihd;
// 个人觉得这两个协议还是分开好,虽然写在一次方便,看起来少,但我觉得逻辑上是比较混乱的
// 这是传出去的协议
@protocol UIHDelegate <NSObject>
- (void)hdOne;
- (void)hdTwo:(NSString *)name;
- (void)hdThree:(NSString *)name age:(NSString *)age;
@end
// 这是遵守JS里方法的协议,必须得遵守JSExport协议
@protocol JSDelegate <JSExport>
// 这里的方法名称和html的函数名称必须相同,同时这个方法的返回值,html里面也能取的到,也相当于另一种传值
- (NSString *)one;
- (void)two:(NSString *)name;
// 多参数写法一:注意,js那边的函数必须驼峰命名
- (void)three:(NSString *)name age:(NSString *)age;
// 多参数写法二:直接完全和js一样,但后面参数面前不能加名字
//- (void)threeAge:(NSString *)name :(NSString *)age;
@end
// 遵守刚刚写的js协议,然后在.m实现,再通过另一个协议传出去
@interface UIHD : NSObject <JSDelegate>
@property (nonatomic, weak) id <UIHDelegate> delegate;
self.uihd .m文件
#pragma mark -- JSDelegate
- (NSString *)one {
// 代理
if ([self.delegate respondsToSelector:@selector(hdOne)]) {
[self.delegate hdOne];
}
NSLog(@"one");
return @"one";
}
- (void)two:(NSString *)name {
if ([self.delegate respondsToSelector:@selector(hdTwo:)]) {
[self.delegate hdTwo:name];
}
NSLog(@"two %@", name);
}
- (void)three:(NSString *)name age:(NSString *)age {
if ([self.delegate respondsToSelector:@selector(hdThree:age:)]) {
[self.delegate hdThree:name age:age];
}
NSLog(@"three %@, %@", name, age);
}
html代码
// html代码 创建对象,让UIWebView在外面监听
var obj;
// 调用OC方法,并且获取方法返回值
var returnValue = obj.threeAge("老王", "18");
WKWebView
说到WKWebView就一把心酸泪了,最近公司突然说搞h5界面,并且得用性能比较好的,而且还是保持用原生,不用第三方的情况下。就这样,默默的跳进了这个坑里,特别我这边的前端当时还写错html代码,然后我一直以为是我的错~.~,好吧,吐槽到此为止,直接上代码
OC调用JS
/** !!必须在加载完之后才能调用
* 调用的函数,传参数则自己拼接到 () 里面
*/
NSString *js = @"tapBtnThree()";
[self.webView evaluateJavaScript:js completionHandler:^(id _Nullable object, NSError * _Nullable error) {
if (error) {
NSLog(@"error = %@", error);
}else {
NSLog(@"object = %@", object);
}
}];
// configuration:WKWebViewConfiguration类,自己查这个是什么鬼
/** !!!注意,这个方法不要填self,连weakSelf都不行,不然会一直强持有,所以这里得重新建一个控制器,当然你也可以想一下其他办法
* @prama Handler:回调人
* @prama name:js方法名称
*/
WKMD *delegate = [[WKMD alloc] init];
delegate.delegate = self;
/**
* @prama Handler:代理
* @prama name:JS发送消息的名字 JS发送消息格式: window.webkit.messageHandlers.hehe.postMessage(message)
*/
[configuration.userContentController addScriptMessageHandler:delegate name:@"one"];
@protocol WKMDDelegate <NSObject>
// 通过代理传js发送的消息出去
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
@end
@interface WKMD : NSObject <WKScriptMessageHandler>
@property (nonatomic, weak) id <WKMDDelegate> delegate;
@end
WKMD .m文件
#pragma mark -- WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"WKMD调用代理");
if ([self.delegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
[self.delegate userContentController:userContentController didReceiveScriptMessage:message];
}
}
html代码
// 这是html里面的代码
// 消息
var message = {
'method' : 'hello',
'param1' : 'liuyanwei',
};
// 发送消息 window.webkit.messageHandlers.约定好的消息名.postMessage(消息)
window.webkit.messageHandlers.three.postMessage(message);
WKWebView9.0版本以下加载本地html问题
- (void)loadWeb {
// 原理就是9.0以下,把文件移到临时文件夹
// 9.0以上
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
// 取本地html文件路径
NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
if (path) {
// 获取本地html的url和资源的url(就是bundle的url)
[self.webView loadFileURL:[NSURL fileURLWithPath:path] allowingReadAccessToURL:[NSBundle mainBundle].resourceURL];
}
}else {
// 9.0以下
// 获取本地文件夹的路径(必须得是蓝色文件夹 Create folder references)
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"devyellow_8.0"];
if(path) {
NSURL *fileUrl = [NSURL fileURLWithPath:path];
// 把文件夹转到tmp目录
fileUrl = [self fileURLForBuggyWKWebView:fileUrl];
NSURL *realUrl = [NSURL fileURLWithPath:[fileUrl.path stringByAppendingString:@"/index.html"]];
NSURLRequest *request = [NSURLRequest requestWithURL:realUrl];
[self.webView loadRequest:request];
}
}
}
// 9.0以下将文件夹copy到tmp目录
- (NSURL *)fileURLForBuggyWKWebView:(NSURL *)fileURL {
NSError *error = nil;
if (!fileURL.fileURL || ![fileURL checkResourceIsReachableAndReturnError:&error]) {
return nil;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *temDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
[fileManager createDirectoryAtURL:temDirURL withIntermediateDirectories:YES attributes:nil error:&error];
NSURL *dstURL = [temDirURL URLByAppendingPathComponent:fileURL.lastPathComponent];
[fileManager removeItemAtURL:dstURL error:&error];
[fileManager copyItemAtURL:fileURL toURL:dstURL error:&error];
return dstURL;
}
WKWebView这里还有个坑,当时还有个需求,就是本地的html加载的时候要从我这边获取值才能加载,WKWebView它又不能在加载的时候从我这边获取值,不像UIWebView能直接用一个对象调用OC方法,然后传一个返回值给html。经过各种姿势的查资料,终于想出了一个解决办法,就是加载之前,把要传的值,放到webView的缓存里,然后前端自己从缓存里取值加载!~不废话,直接上代码
/** 其实这个格式和字典一样
* key: 和前端约定好的key
* value: 需要传的值
*/
NSString *sipNum = [NSString stringWithFormat:@"localStorage.setItem(\"key\", '%@');", @"value"];
/** 添加脚本
* param injectionTime:WKUserScriptInjectionTimeAtDocumentStart在加载之前注入
* param forMainFrameOnly:是否主窗口(其实我也不清楚这个是啥)
*/
WKUserScript *script = [[WKUserScript alloc] initWithSource:sipNum injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[configuration.userContentController addUserScript:script];