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];



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值