不久之前,项目中用到了UIWebView加载js的功能,之前使用webView都是简单使用,没考虑很多与js交互的地方,虽然现在项目完成了,但是回头看看这方面的知识还是有些茫然,在此记录一点,然后后续如果有用更多的话再来进行补充。
需求
封装个view,提供给开发者使用,暴露两个方法以供调用:
1、是调用initWithXXX进行初始化位置等等参数配置;
2、调用loadH5PageWithSuccessBlock:failureBlock:让view中的webView加载H5页面显示出来即可。
其主要实现就是webView加载一个URL的过程,给开发者一个加载失败和完成的回调。
UIWebView的代理方法
1、首先实现的是UIWebView的shouldStartLoadWithRequest:navigationType:代理方法
注:对于返回的是unicode编码的字符串,用 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding 进行返回UTF8编码的字符串。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSLog(@"URL : %@", [request.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]); // click event if ([request.URL.host isEqualToString:@"call_close"]) {// notify click close if (self.gh_clickCloseBlock) { self.gh_clickCloseBlock(webView, request.URL.absoluteString); } [self stopTimer:self.loadStatusCheckTimer]; return NO; } if ([request.URL.host isEqualToString:@"on_click"]) { NSString *URL_String = [request.URL.path substringFromIndex:1]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:URL_String]]; // notify click content if (self.gh_clickBlock) { self.gh_clickBlock(webView, URL_String); } return NO; } return YES; }
如果上述回调中返回YES,那么webView会进行加载html页面,执行 webViewDidStartLoad: 回调。
每次在页面中的点击都可能会“截获”该URL,并会再次调用上述回调,那么通过URL的scheme和host就可以判断是什么动作事件,
例如:
如果是host为“call_close”,则可以做一些关闭的操作:移除view,停止定时器等,并添加关闭页面的block回调给开发者。
如果是host为“on_click”,则可以添加点击页面内容的block回调给开发者。
2、开始加载的回调
- (void)webViewDidStartLoad:(UIWebView *)webView { NSLog(@"start, %d", webView.loading); }
3、如果加载失败的话会调用 didFailLoadWithError:
页面加载超时等失败的时机回调给开发者,但是对于请求中HTTP的状态码error的时候并不能判断出来,
对于这部分具体可以用NSURLConnection判断response的statusCode,如果是400以上即可回调失败,如果是成功响应再允许webView加载这个URL,
我们可以只要response,不需要receiveData,所以在得到response之后将connection cancel掉。
参考相关:http://stackoverflow.com/questions/1061364/how-do-i-get-the-last-http-status-code-from-a-uiwebview
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { NSLog(@"failure : %@", error.description); // page加载失败的callBack if (self.gh_loadFailBlock) { self.gh_loadFailBlock(webView, error.code); } }
4、page加载完成的回调,启动定时器进行检查,不断获取js中关于image的加载状态,以便获取回调时机。
- (void)webViewDidFinishLoad:(UIWebView *)webView { NSLog(@"finish, %d", webView.loading); [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"]; [webView stringByEvaluatingJavaScriptFromString:@"document.body.style.webkitTouchCallout='none';"]; if(!injectedPageLoadedJS) { injectedPageLoadedJS = YES; [self stopTimer:self.loadStatusCheckTimer]; self.loadStatusCheckTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(checkLoadStatus:) userInfo:nil repeats:YES]; } }
加载html的时候,我们的js会执行对url进行请求的操作,其中会有请求image资源,但是在webView这里,当html这个page加载完成时就会调用finish回调,如果加载H5页面的成功回调写在这里,可能会出现js还没有成功请求到图片资源渲染到页面上,你已经回调开发者加载成功。所以期间从finish到image异步请求完成,中间有段时间需要等待。
解决方式是在页面加载完成finish之后,通过NSTimer不断地去获取js加载image状态的返回值,直到获取成功的返回值,做出给开发者的block。
与js简单交互
和前端协商将js中标示image异步请求成功的变量定为window.__loadImageStatus__; 这样js在拿到image请求成功的回调之后,会将此变量赋值为imgLoaded,这个js的值可以通过webView的stringByEvaluatingJavaScriptFromString:方法的返回值拿到。
何时去取到这个值,需要在webView加载完html页面时finish回调之后取,可以添加一个定时器,每隔0.5s中通过stringByEvaluatingJavaScriptFromString:拿到返回值,判断是否是imgLoaded,如果是,说明此时图片异步加载成功,图片已经展示,在此可以作为给开发者的成功回调。
- (void)checkLoadStatus:(NSTimer *)timer { NSString *evalString = [_webH5View stringByEvaluatingJavaScriptFromString:@"window.__loadImageStatus__;"]; NSLog(@"+++++js 返回的status值:%@", evalString); if([evalString isEqualToString:@"imgLoaded"]) { //Web page has finished. Shut down the timer. [self stopTimer:timer]; injectedPageLoadedJS = NO; NSLog(@"成功获取到imgLoaded,js将image load 完成的回调值发给");// 成功回调 if (self.gh_loadAdSuccessBlock) { self.gh_loadAdSuccessBlock(_webH5View, _request.URL.absoluteString); } self.count = 0; } else { if (self.count > 10) { [self stopTimer:timer]; } else { self.count += 1; } } }
注:最好添加一个计数器,当10次(也就是5s)还没有获取到js关于image加载成功的返回值,就可以将timer等相关对象移除,避免长时间等待,影响交互体验。