1. 程式人生 > >WKWebView和UIWebView載入本地html和JS互動各種坑解決辦法

WKWebView和UIWebView載入本地html和JS互動各種坑解決辦法

因為蘋果的檔案機制,所有的資原始檔都相當於放在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];