JS与OC互相调用(一)

UIWebView 拦截URL

老项目大多使用的UIWebView加载网页或者html静态页面(旧的SDK貌似已经废弃转向WKWebView)

1. 加载网页方法
    NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]];
    [_webView loadRequest:request];
2. OC调用JS
    NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"这里是JS中alert弹出的message"];
    [_webView stringByEvaluatingJavaScriptFromString:jsStr];
showAlert是要调用页面中js的函数名,后面跟着的是参数
webview对象通过调用stringByEvaluatingJavaScriptFromString这个方法来调用JS
3. JS调用OC

原理:用JS发起一个假的URL请求,然后利用UIWebView的代理方法拦截这次请求,然后再做相应的处理。

        
        function loadURL(url) {
            var iFrame;
            iFrame = document.createElement("iframe");       
            iFrame.setAttribute("src", url);
            iFrame.setAttribute("style", "display:none;");
            iFrame.setAttribute("height", "0px");
            iFrame.setAttribute("width", "0px");
            iFrame.setAttribute("frameborder", "0");
            document.body.appendChild(iFrame);
            // 发起请求后这个 iFrame 就没用了,所以把它从 dom 上移除掉
            iFrame.parentNode.removeChild(iFrame);
            iFrame = null;
        }
        function firstClick() {
            loadURL("firstClick://shareClick?title=分享的标题&content=分享的内容&url=链接地址&imagePath=图片地址&six=");
        }
tips:   
    1.为什么自定义一个loadURL 方法,不直接使用window.location.href?
        答:因为如果当前网页正使用window.location.href加载网页的同时,调用window.location.href去调用OC原生方法,会导致加载网页的操作被取消掉。
    同样的,如果连续使用window.location.href执行两次OC原生调用,也有可能导致第一次的操作被取消掉。所以我们使用自定义的loadURL,来避免这个问题。
    loadURL的实现来自关于UIWebView和PhoneGap的总结一文。
    2.为什么loadURL 中的链接,使用统一的scheme?
        答:便于在OC 中做拦截处理,减少在JS中调用一些OC 没有实现的方法时,webView 做跳转。因为我在OC 中拦截URL 时,根据scheme (即haleyAction)来区分是调用原生的方法还是正常的网页跳转。然后根据host(即//后的部分getLocation)来区分执行什么操作。
    3.为什么自定义一个asyncAlert方法?
        答:因为有的JS调用是需要OC 返回结果到JS的。stringByEvaluatingJavaScriptFromString是一个同步方法,会等待js 方法执行完成,而弹出的alert 也会阻塞界面等待用户响应,所以他们可能会造成死锁。导致alert 卡死界面。如果回调的JS 是一个耗时的操作,那么建议将耗时的操作也放入setTimeout的function 中。
3.1 OC的UIWebView的代理方法
    当然,有时候我们在JS中调用OC方法的时候,也需要传参数到OC中,怎么传呢?
    就像一个get 请求一样,把参数放在后面,那么如果获取到这些参数呢?
    所有的参数都在URL的query中,先通过&将字符串拆分,在通过=把参数拆分成key和实际的值。
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL * url = [request URL];
   if ([[url scheme] isEqualToString:@"firstclick"]) {
        NSArray *params =[url.query componentsSeparatedByString:@"&"];
       for(id obj in params){
           NSLog(@"%@", obj);
       }
        NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
        for (NSString *paramStr in params) {
            NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
            if (dicArray.count > 1) {   //这个判断没有作用,默认的没有值的参数会自动补空串
                NSString *decodeValue = [dicArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
                [tempDic setObject:decodeValue forKey:dicArray[0]];
            }
        }
       UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"方式一" message:@"这是OC原生的弹出窗" delegate:self cancelButtonTitle:@"收到" otherButtonTitles:nil];
       [alertView show];
       NSLog(@"tempDic:%@",tempDic);
        return NO;
    }
    return YES;
}
tips:
    UIWebView 有一个代理方法,可以拦截到每一个链接的Request。
    return YES,webView 就会加载这个链接;
    return NO,webView 就不会加载这个连接。
    我们就在这个拦截的代理方法中处理自己的URL。

注意:
    1. JS中的firstClick,在拦截到的url scheme全都被转化为小写。
    2.html中需要设置编码,否则中文参数可能会出现编码问题。
    3.JS用打开一个iFrame的方式替代直接用document.location的方式,以避免多次请求,被替换覆盖的问题。

#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *URL = request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"myscheme"]) {
        [self handleCustomAction:URL];
        return NO;
    }
    return YES;
}
- (void)handleCustomAction:(NSURL *)URL
{
    NSString *host = [URL host];
    if ([host isEqualToString:@"scanClick"]) {
        NSLog(@"扫一扫");
    } else if ([host isEqualToString:@"shareClick"]) {
        [self share:URL];
    } else if ([host isEqualToString:@"getLocation"]) {
        [self getLocation];
    } else if ([host isEqualToString:@"setColor"]) {
        [self changeBGColor:URL];
    } else if ([host isEqualToString:@"payAction"]) {
        [self payAction:URL];
    } else if ([host isEqualToString:@"shake"]) {
        [self shakeAction];
    } else if ([host isEqualToString:@"goBack"]) {
        [self goBack];
    }
}
这里根据不同的url调用不同的方法,原生的逻辑具体就写在这些方法里。
回调就用stringByEvaluatingJavaScriptFromString这个方法。
如果回调执行的JS方法带参数,而参数不是字符串时,不要加单引号,否则可能导致调用JS方法失败。
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userProfile options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *jsStr = [NSString stringWithFormat:@"loginResult('%@',%@)",type, jsonStr];
[_webView stringByEvaluatingJavaScriptFromString:jsStr];

微信公众号:
JS与OC互相调用(一)_第1张图片
代码及效果图后续上传到公众号,回复“JS交互”可获取全部代码

你可能感兴趣的:(iOS开发)