这个功能已经写完快两个月了,马上要把这个H5功能改成原生的,也就是说之前的代码暂时没有别的地方需要用,所以准备匿了,现在来分享一下过程应该不算晚
说一下这个功能在Android的Webview上实现思路是一样的,但是iOS这边稍微麻烦了一点,主要是因为UIWebview和WKWebview的区别以及iOS的版本问题,因此需要兼容两种办法——iOS11.*以前和以后
需求:在WKWebview的H5页面中需要选择图片(调用自定义的图片选择器而不是系统相册)然后再页面中预览,点击上传按钮后开始上传(由H5进行上传)
语言用的是最新的Swift4.0,配合之前的一篇文章WKWebview与Javascript互相调用传参(Swift)服用更佳

Part 1. 获取图片并在H5中预览

首先第一步是H5中点击选择图片按钮唤起自定义图片选择器,这个是Javascript和原生的交互就不再提了
唤起了自定义图片选择器后选择完图片获取的图片数据是UIImage格式的(系统相册返回的也是)这玩意儿是无法直接返回给H5的,那么我们发现选择图片返回的还有一个Path信息(图片在手机系统里的物理路径)这个Path信息唯一的毛病是系统限制无法读取的,因为安全权限的问题,所以我们必须要把它存在应用程序的沙盒路径里。

你非要跟我犟我也没办法,如果你碰到Promise dined的问题的时候回来看这里

存储图片的代码

let fullPath = URL(fileURLWithPath: NSHomeDirectory().appending("/Documents/WKWebviewTmpImg/").appending(filename), isDirectory: false)
try! UIImageJPEGRepresentation(_photos![i], 1.0)?.write(to: fullPath, options: Data.WritingOptions.atomicWrite)

代码的意思查查就明白了,这个是存储JPEG/JPG图片的代码_photos![i]这个是获取到的UIImage对象
然后怎么提供给H5呢,如果你直接给沙盒地址,H5会告诉你:为了安全起见,HTML不支持读取file://开头的文件。必然的,如果你直接返回沙盒的物理路径的话,在类似这种标签里,必须有个协议头,那物理路径的协议头肯定是file://而不是http。如果你用了http的话,那就更加找不到了,因为浏览器会去喜马拉雅山顶上去找这个文件在哪。
这里就要用到自定义协议头了,比如说wxfile://,哦,不好意思,这个是微信的。
说到这里,我希望你能百分百相信我这个方案的正确性,因为这个方案是参考微信内置浏览器的图片选择+上传方案来做的。
大概思路是:

  1. 你得起一个自定义的协议头,千万不要和其他app的自定义协议头冲突,虽然不见得会有什么影响,毕竟这是你的App
  2. 然后对路径进行编码,确保在反编码后你还能知道这个文件在哪,比如说给路径base64加密之类的
  3. 把这个路径回调给H5

至此,我们完成了获取图片实现预览的上半场,接下来是下半场。
当你把类似“wxfile://JXU3NzBCJXU0RUMwJXU0RTQ4JXU3NzBC.jpg”这样的加密图片路径回调给H5,并且在H5里以

<img src="wxfile://JXU3NzBCJXU0RUMwJXU0RTQ4JXU3NzBC.jpg" />

的样子展现后依然无法预览图片,因为浏览器根!!!本!!!不知道这个wxfile://是什么鬼啊!!!
那么问题来了,我们怎么把这个地址当成图片输出来呢?
答案只有一个:拦截资源请求
也就是传说中的使用NSURLProtocol来实现资源请求的拦截并替换,关于这块我想OC写多了的人都知道是怎么回事,这里就不多说了。
但是!!!我可以告诉你们的是在WKWebview里压根就不再使用NSURLProtocol来做代理啦!!!惊不惊喜?意不意外?开不开心?
到了这里,如果你是写OC的,请配合https://github.com/yeatse/NSURLProtocol-WebKitSupport食用,疗效更好!
那么在Swift版本里,使用上述方式也是可以的,具体使用方式可以参考github里的使用说明
在注册好了协议头后,当H5发起对这个协议头的资源请求的时候就会自动被捕获到然后代理到你的处理方法里,接下来只需要自己定义一个Response对象并且返回给H5就可以实现图片预览了,代码如下:

1. 获取图片数据
NSString* tmpUrl = [[NSHomeDirectory() stringByAppendingString:@"/Documents/WKWebviewTmpImg/"] stringByAppendingString:self.request.URL.host];
NSURL* fileUrl = [NSURL fileURLWithPath:tmpUrl isDirectory:NO];
UIImage* img = [UIImage imageWithContentsOfFile:tmpUrl];
NSString* imgMime = @"image/jpg";
NSData* imgData = UIImageJPEGRepresentation(img, 1.0);

2. 伪造Response数据
NSURLResponse* response = [[NSURLResponse alloc] initWithURL: fileUrl MIMEType: imgMime expectedContentLength: imgData.length textEncodingName:nil];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
[self.client URLProtocol:self didLoadData:imgData];
[self.client URLProtocolDidFinishLoading:self];

代码的意思自己读一下吧,我就不解释了。
然后我们要说的是针对iOS11.*的系统版本,我们WKWebview提供了一个很流弊的方法,可以在Swift里很方便地处理拦截请求。
大意就是实现WKURLSchemeHandler的接口
首先,你需要注册一下scheme,在配置WKWebview的WKWebViewConfiguration的时候加一条

config.setURLSchemeHandler(self, forURLScheme: "wxfile")

然后在WKURLSchemeHandler里的处理方法里对于伪造Response的过程和代码基本与OS差不离,也就是把OC的代码翻译成Swift的区别,相信我基本上一行不多一行不少
如果你的项目还是不能预览图片,请在下方留言给我,个人不善于直播吃翔,谢谢合作。

Part 2. 获取图片并上传

鉴于H5无法直接展示物理路径的图片,同样也无法直接把物理路径进行POST上传到服务器或者第三方存储,一般第三方存储类似七牛或者又拍云的最多支持base64的数据上传,也就是form的multipartform-data提交
所以我们仅仅拿到图片的物理路径或者是自定义协议头的路径都是并没有什么卵用的,你得拿到图片的base64数据才行
之前我说的我的方法是参照微信的套路,那么同时我也看了微信的上传接口,其中有一个似乎叫getLocalImageData的接口是用来获取图片数据用于上传的,所以我们也可以设计这么一个方法用于获取图片的base64数据进行上传
还是按照WKWebview和Javascript交互的套路由H5去请求原生接口,然后原生根据自定义协议头的图片路径转换成物理路径,然后根据物理路径读取图片的base64

let img = UIImage(contentsOfFile: NSHomeDirectory().appending("/Documents/WKWebviewTmpImg/").appending(imgFile))
var imgData = UIImageJPEGRepresentation(img!, 1.0)
let imgBase64 = imgData?.base64EncodedString()

然后再回调给H5就完成了获取图片的base64数据功能,后面的上传部分就我就不再详细说明了,麻烦让你的前端同事好好折腾吧。

以上。