本文记录之前帮助朋友开发该功能的填坑之旅,需求为tableView显示图片,图片固定宽度为屏幕宽度高度自适应,保证不失真。效果如图所示:
乍看该需求可能并不难甚至简单的很,若API已经给予每个图片的宽高比例,那该功能很容易实现。但是对于一些初创型公司,为减少开发成本,图片的上传下载使用的可能为类似于七牛等云存储服务。图片存储在七牛服务器上,前端传递给后台的仅仅是个图片链接字符串而已。后台人员无法获取图片宽高或因为其他原因无法获取,这时就需要我们自己计算其真实显示高度。
刚开始分析需求时,想到的是通过AutoLayout进行实现,既然Cell中的Label可以实现自适应,那图片为什么不呢?
因此第一个种想法是给图片添加上下左右的周边约束,将ContentModel设置为Aspect Fill,图片加载简单使用sd_setImageWithUrl:
,然后设置TableView自适应进行实现。
//行高自适应
_tableView.rowHeight = UITableViewAutomaticDimension;
//预计行高,与真实行高接近即可
_tableView.estimatedRowHeight = 44.0;
但效果却事与愿违,图片虽未失真,但Cell上却存在很多留白。只有TableView滑动重新显示时才能达到正常的状态,而且会发生动态调整缩放,简直丑到爆。显然是刷新时机不正确。之后尝试延时刷新或更新约束等方式,但是结果仍然不是很理想。最后摒弃AutoLayout,通过手动计算图片真实高度进行实现,该篇文章即由此而来。若有其他更好的方案,欢迎评论提出。
这里我们需要使用到SDWebImage中SDWebImageDownloader
与SDImageCache
这两个类来实现功能,分别为图片下载类于图片缓存类。具体代码如下,假设以获取所需图片网址数组为_picArr。
首先借助SDImageCache
进行判断对应图片是否缓存。若已缓存,获取其真实需要显示高度(当前屏幕宽度乘图片比例即可),将获取到的高度存入高度字典中。若不存在缓存,使用SDWebImageDownloader
进行下载,在下载结束后同样计算高度并缓存图片。SDWebImageDownloader
下载图片为异步,无法保证图片按顺序下载完成,因此这里使用字典存储而非数组。字典键值对存储的即为所对应行应显示的高度。
for (NSInteger i = 0; i < _picArr.count; i++) {
//获取图片网址
NSString *picUrl = _picArr[i];
//根据图片网址获取缓存
UIImage *cachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:picUrl];
if (cachedImage) {//若存在 计算图片真实需要显示高度 存入字典
CGFloat height = cachedImage.size.height * self.view.bounds.size.width / cachedImage.size.width;
[_rowHeightDict setObject:[NSNumber numberWithFloat:height] forKey:[NSNumber numberWithInteger:i]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
} else {//不存在缓存 使用SDWebImageDownloader下载
[[SDWebImageDownloader sharedDownloader]downloadImageWithURL:[NSURL URLWithString:picUrl] options:SDWebImageDownloaderProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (finished) {
//对现在图片进行缓存
[[SDImageCache sharedImageCache] storeImage:image forKey:picUrl toDisk:YES];
CGFloat height = image.size.height * self.view.bounds.size.width / image.size.width;
[_rowHeightDict setObject:[NSNumber numberWithFloat:height] forKey:[NSNumber numberWithInteger:i]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
}
}
当前显示的行数需返回行高字典的键值对个数。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _rowHeightDict.count;
}
若图片已缓存,加载缓存图片,若无缓存,显示默认placehold图片。
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReusableID];
UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:_picArr[indexPath.row]];
if (!image) {
cell.photoImageView.image = [UIImage imageNamed:@"placehold"];
} else {
cell.photoImageView.image = image;
}
return cell;
}
返回行高部分也会相应容易很多,若已计算返回计算好的行高,若无返回默认行高。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (_rowHeightDict[[NSNumber numberWithInteger:indexPath.row]]) {
return [_rowHeightDict[[NSNumber numberWithInteger:indexPath.row]] floatValue];
}
return 200.0;
}
demo已做分离,尽可能以最简单的形式展示。
demo下载链接
UITableView异步加载图片自适应填坑之旅demo