写给 iOS 程序员的 Weex 教程(6):本地图片加载和上传

2017/3/24 22:36 下午 posted in  iOS comments

由于 Weex 运行在 JavasSriptCore 环境里,它无法直接获取和处理本地图片。所以我们需要有一个变通的方式来实现。
涉及本地图片的操作主要有两种:展示和上传到服务器,下面就这两方面分别说明。(什么?你说编辑?建议你还是放过 weex 吧,用本地代码又快又省事。)

显示本地图片

weex 会把网络图片的请求转发到我们支持 WXImgLoaderProtocol 的对象上。当需要请求图片的时候,会调用此方法:

- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^)(UIImage *image,  NSError *error, BOOL finished))completedBlock;

这个方法接受一些参数,异步加载图片后返回一个 UIImage 对象。这里就是我们用来支持本地图片的地方。我们可以约定一个规则,通过 url 参数来告诉我们需要加载哪个图片。这里,我建议不要定义特殊协议的 url。因为不利于代码在游览器环境的复用。

我们的方案是把本地图片放一份在网上,然后通过在图片地址后面加参数的形式识别图片在本地对应的名字,类似这样:

http://bjimg5.appdao.com/image/936167445?local=backIcon

参数 local 用来表明是本地图片,值是对应的文件名。拿到文件名后就可以用 imageNamed: 方法获得。
这个实现方式有两个好处:

  1. 游览器端不用修改代码就能直接用;
  2. 如果万一忘记添加图片到工程里,也是能展示图片;

不过需要注意,imageNamed: 方法默认获取的是 mainBundle 里的图片。如果将来通过热更新,添加了新图片,那这些图片是不在 mainBundle 里。直接通过文件名是找不到的。此时需要使用图片基于 mainBundle 的相对路径来:

+ (UIImage*)ark_imageFromName:(NSString*)name
{
    UIImage *img = [UIImage imageNamed:name];
    if (!img) {
        NSString *mediaDir = /* get media folder */;
        NSString *path = [mediaDir stringByAppendingPathComponent:name];
        path = [self relativePathToMainBundle:path];
        img = [UIImage imageNamed:path];
    }
    
    return img;
}

+ (NSString*)relativePathToMainBundle:(NSString*)path
{
    NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
    NSString *appDirectory = [mainBundlePath stringByDeletingLastPathComponent];
    NSString *relativePath = [path stringByReplacingOccurrencesOfString:appDirectory withString:@".."];
    return relativePath;
}

通过这样的方式,就能通过文件名同时兼容工程里的图片和某个目录下的图片。

上传图片

Weex 是不能直接操作 iOS 系统里的文件,也就无法直接上传文件。网上有些方案是通过把图片改成 Base64 编码的字符串,然后传递字符串到 weex。这个方案的缺点是如果图片如果较大,对性能和内存都是很大消耗。
我的建议是直接在本地代码里写好上传,然后把上传后的网络地址等数据返回给 weex。这样的好处是能做到后台上传,也能充分利用 iOS 平台的性能。

上传图片的具体方法很简单,各家也不同,我就不具体写了。我就说两个在这个过程中会遇到的问题。

选择图片

这是大家会遇到的第一个问题。Weex 没有封装好系统照片选择器的控件。我的方案是自定义一个 module,然后通过 module 弹出系统的图片选择控件或自己的图片控件。

用户选择完图片后,将图片另存一份到自己的目录。然后构造 file 协议的图片地址,返回给 weex。Weex 拿到这个地址后去显示在界面上。

BOOL success = [imageData writeToFile:path atomically:YES];
if (success) {
    callback([NSURL fileURLWithPath:path].absoluteString);
}

这里还没有完。要真的显示出来,还需要改写一下刚才提到的 WXImgLoaderProtocol 对象的 downloadImageWithURL:imageFrame:userInfo:completed: 方法。需要在这个方法里识别 file 协议的地址,然后用 [UIImage imageWithContentsOfFile:] 返回图片对象。

通过这两步,就完成了图片的选择和显示。

上传进度显示

Weex 的 module 协议只有一个回调,也只能用一次。这就没法做到持续的更新上传文件的进度。这里有一个丑陋点的变通方案。

方案很简单,直接通过 Weex 提供的 globalEvent 模块,持续给 weex 代码发通知。Weex 里就监听对应的事件。方法很丑陋,但是现阶段唯一方案。

这个方法也提示我们,如果我们想做一个本地代码持续更新内容到 weex 代码的事情,也是用 globalEvent

结语

本系列文章都结束了。只讲了在开发会遇到的常见问题,希望大家能举一反三,解决自己遇到的其他问题。
Weex 现在的最新版已经开始支持 vuejs,这也是官方推荐方式。自家的语法将成为了历史。我觉着这是好事,Weex 现在可以专注自己的渲染层,做好 Virtual DOM 到本地控件的转换。我的项目现在已经将所有代码迁移到 vuejs 下,基本按照官方文档做就好。有空的话可能会写一下 weex + vuejs 遇到的坑。