在前面的文章中,我们介绍了UIWebView
、WKWebView
一些使用,与JS的交互和一些坑,相信看过的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。
本文是本系列文章的最后一篇,主要为小伙伴们分享下Safari调试、与前端的配合以及实际应用中一些需求的实现等:
关于文中提到的一些内容,这里我准备了个Demo,有需要的小伙伴可以下载。
在前面的文章中,查看网页的COOKIE,其实已经用到了Safari调试。笔者觉得Safari调试功能真的很有用,通过它可以轻松定位问题的所在。也因此,公司中App一旦有问题出现,不管是客户端的问题,还是前端的问题,找问题的重任都落到了笔者的身上呢。这一度是一个困扰。想象一下,h5页面的一个bug,App端帮忙快速定位,并且告知h5相关开发人员该如何修复,是多么伟大的一件事情。
下面来简单讲讲怎么用Safari调试。
在Mac的Safari偏好设置中,开启开发菜单。具体步骤为:Safari -> 偏好设置… -> 高级 -> 勾选在菜单栏显示“开发”菜单。
至此,问题找到了,只要告之前端开发人员即可,让他修复即可。
实际遇到的问题可能要复杂的多,可以通过断点,以及控制台打印一些js变量的值,DOM操作来寻找问题,解决问题。希望可以帮助到小伙伴们。
这个其实在App开发中,比较重要。比如常见的微信、支付宝App等,都有自己的UserAgent
,而UA最常用来判断在哪个App内,一般App的下载页中只有一个按钮"点击下载",当用户点击该按钮时,在微信中则跳转到应用宝,否则跳转到AppStore。那么如何区分在哪个App中呢?就是js判断UA。
//js中判断if (navigator.userAgent.indexOf("MicroMessenger") !== -1) { //在微信中}
关于自定义UA,这个UIWebView
不提供Api,而WKWebView
提供Api,前文中也说明过,就是调用customUserAgent
属性。
self.webView.customUserAgent = @"WebViewDemo/1.0.0"; //自定义UA,只支持WKWebView
而有没有其他的方法实现自定义浏览器UserAgent呢?有。
//最好在AppDelegate中就提前设置@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. //设置自定义UserAgent [self setCustomUserAgent]; return YES;}- (void)setCustomUserAgent{ //get the original user-agent of webview UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero]; NSString *oldAgent = [webView stringByEvaluatingJavascriptFromString:@"navigator.userAgent"]; //add my info to the new agent NSString *newAgent = [oldAgent stringByAppendingFormat:@" %@", @"WebViewDemo/1.0.0"]; //regist the new agent NSDictionary *dictiOnnary= [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", newAgent, @"User-Agent", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];}@end
上面的代码,展示了在原有UserAgent
的基础上,添加一些自定义的内容。
ImgAddClickEvent.js
文件,来实现给所有无默认点击事件的
添加点击事件。
//获取所有img标签var imgs = document.getElementsByTagName("img");//获取所有的imgUrlvar imgUrls = new Array();var x = 0;var y = 0;var width = 0;var height = 0;for (var i = 0; i var img = imgs[i]; //如果图片链接存在 if (img.src || img.getAttribute('data-src')) { //添加到图片链接数组中 imgUrls.push(img.src || img.getAttribute('data-src')); //如果图片没有默认的onclick事件,且父元素不是a标签,则添加onclick事件,当用户点击时,把图片链接回传给Native if (!img.onclick && img.parentElement.tagName !== "A") { //给图片添加下标的属性 img.index = i; //记录下标 //添加点击事件,并且回传选中的图片链接、下标、屏幕上的位置、全部的图片数组等 img.Onclick= function() { x = this.getBoundingClientRect().left; y = this.getBoundingClientRect().top; x = x + document.documentElement.scrollLeft; y = y + document.documentElement.scrollTop; width = this.width; height = this.height; var imgInfo = { imgUrl: this.src || this.getAttribute('data-src'), x: x, y: y, width: width, height: height, index: this.index, imgUrls: imgUrls }; //UIWebView使用 h5ImageDidClick(imgInfo); } } }}function h5ImageDidClick(info) { //WKWebView使用 window.webkit.messageHandlers.imageDidClick.postMessage(info);}
下面分别介绍UIWebView
和WKWebView
如何实现。
UIWebView
直接使用JavascriptCore
给添加
onclick
方法为OC的实现即可。
- (void)webViewDidFinishLoad:(UIWebView *)webView { [self convertJSFunctionsToOCMethods];}- (void)convertJSFunctionsToOCMethods { //获取该UIWebview的Javascript上下文 //self持有jsContext //@property (nonatomic, strong) JSContext *jsContext; self.jsCOntext= [self.webView valueForKeyPath:@"documentView.webView.mainFrame.JavascriptContext"]; //先注入给图片添加点击事件的js //防止频繁IO操作,造成性能影响 static NSString *jsSource; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ jsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ImgAddClickEvent" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]; }); [self.jsContext evaluateScript:jsSource]; //替换回调方法 self.jsContext[@"h5ImageDidClick"] = ^(NSDictionary *imgInfo) { NSLog(@"UIWebView点击了html上的图片,信息是:%@", imgInfo); };}
而WKWebView
实现,需要使用WKUserScript
和scriptMessageHandler
,下面简单介绍下,详细实现,见Demo。
WKWebView
的UIViewController
中实现如下
/** 页面中的所有img标签添加点击事件 */- (void)imgAddClickEvent { //防止频繁IO操作,造成性能影响 static NSString *jsSource; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ jsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ImgAddClickEvent" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]; }); //添加自定义的脚本 WKUserScript *js = [[WKUserScript alloc] initWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO]; [self.webView.configuration.userContentController addUserScript:js]; //注册回调 [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"imageDidClick"];}- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:@"imageDidClick"]) { //点击了html上的图片 NSLog(@"点击了html上的图片,参数为%@", message.body); }}
当我点击这个标签时,因为我添加了onclick事件,在OC端我会接收到回调。因此打印出log
上面无论是
UIWebView
还是WKWebView
,参数中的x,y是不包含自定义scrollView的contentInset的,如果要获取图片在手机屏幕上的位置:
x = x + self.webView.scrollView.contentInset.left;y = y + self.webView.scrollView.contentInset.top;
拿到了这些信息,想必,你可以实现一个十分完美的图片预览效果了。
因为页面中的img标签加载图片的网络请求是由WebView
管理的,所以要想Native接管图片的下载,只有2条路:
- 在页面加载前,把页面中img标签的src换成一张native的占位图或者"",并且把img.src传递到native,在native下载图片或者读取缓存完毕后,再把相应的img标签的src设置成本地的,如img.src ="native cache url"。整体交互是JS->Native, Native -> JS。
NSURLProtocol
拦截WebView
的所有图片请求,交由我们自己管理。
比较有可行性的是方法2。但也只限于UIWebView
。WKWebView
上篇文中说过,虽然有私有Api,但是笔者不推荐使用。
先说下,为何方法1不可行。首先,页面加载前,是在什么时候呢?如果h5不做修改,全部交由Native端处理,是没有办法修改html的,因为UIWebView
和WKWebView
都没有提供一个Api,在图片加载之前告诉你html是什么内容,所以这个方法是走不通的。除非你用loadHTMLString
的方法加载,加载前先替换img的src。但是loadHTMLString
的方法,又加载不到Web端的js和css,只能用于本地拼接完整HTML String
的情况,不适用于一般的场景。So,这条路是走不通的,局限性太大了。
方法2的核心思路就是拦截请求,最核心的是在你的NSURLProtocol
子类中,实现这个方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request { //处理过不再处理 if ([NSURLProtocol propertyForKey:DAURLProtocolHandledKey inRequest:request]) { return NO; } //根据request header中的 accept 来判断是否加载图片 /* { "Accept" = "image/png,image/svg+xml,image/*;q=0.8,*\/*;q=0.5\"; "User-Agent" = "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E269 WebViewDemo/1.0.0"; } */ NSDictionary *headers = request.allHTTPHeaderFields; NSString *accept = headers[@"Accept"]; if (accept.length >= @"image".length && [accept rangeOfString:@"image"].location != NSNotFound) { return YES; } return NO;}
当拦截到图片请求时,再做后续的处理,下面写一些伪代码
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request;}+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return [super requestIsCacheEquivalent:a toRequest:b];}- (void)startLoading { NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; //这里也可以添加一些自定义的header,看具体需求 //标记该request已经处理过 [NSURLProtocol setProperty:@(YES) forKey:DAURLProtocolHandledKey inRequest:mutableReqeust]; //这里NSURLProtocolClient的相关方法都要调用 //比如 [self.client URLProtocol:self didLoadData:data]; .....}- (void)stopLoading { .....}
这部分代码,Demo中并没有完全实现,如果小伙伴们有兴趣研究下具体的实现,可以参考这篇文章, 笔者这里就不多说了。
这个需求,其实我在前面的文章中针对UIWebView
和WKWebView
都已经做了很详细的介绍。这里,简单分享下如何获取分享的内容。
一般分享到微信或者QQ至少需要的参数是
- Title(主标题)
- Description(副标题或者描述)
- ThumbnailImage(缩略图)
- WebpageUrl(h5页面的链接)
这些参数,都怎么获取呢?通过OC->JS的方式,获取。当然你也可以自定义。
var title = document.title;var desc = document.getElementsByTagName("article")[0].textContent; //或者 document.body.innerText; 或者 document.getElementById("yourId").innerText; var thumbnailImageUrl = document.getElementsByTagName("img")[0].src; //或者看需求取哪个var webpageUrl = location.href;
具体如何用OC调用JS获取这些值,这里就不多说了,看过前面文章的小伙伴,可以自行实现。
很多时候,Native与H5交互得深了,必定会有一些更深层次的需求。比如h5想控制页面的pop、push、present,想调用Native的Share,想调用Native的扫描二维码功能,获取扫描结果……
转载自:http://www.jianshu.com/p/52668d5b2e68