WKWebView和UIWebView載入本地html和JS互動各種坑解決辦法
阿新 • • 發佈:2019-01-10
因為蘋果的檔案機制,所有的資原始檔都相當於放在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;
self.uihd .h檔案// 個人覺得這兩個協議還是分開好,雖然寫在一次方便,看起來少,但我覺得邏輯上是比較混亂的 // 這是傳出去的協議 @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);
}
}];
JS給OC傳送訊息
// 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"];
WKMD .h檔案
@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];