简介

近日在制作一个开源加密相册时附带着设计了一个照片浏览器,在进一步优化后发布到了GitHub供大家使用,该框架虽然没有MWPhotoBrowser那么强大,但是使用起来更为方便,操作更符合常规相册习惯,自定义和修改源码也十分简单。
本文主要介绍这个照片浏览器框架的技术要点,如果要深入研究和使用,可以在下面的链接中下载源码。

如果你对这个框架有兴趣,可以点击这里前去GitHub下载源码,欢迎Star与指出不足

效果图

缩略图预览,点击缩略图进入原图浏览,点击底部工具栏可以进入编辑模式。

批量导出与删除,通过底部工具栏操作。

查看原图,单击可以隐藏导航栏和工具栏,支持双击切换缩放状态、捏和手势以及左右滑动切图。

功能与特点

  • block数据源
    照片浏览器的数据源是通过block回调的,通过实现相应的block并且提供数据模型即可完成图片显示。

  • 内存优化
    高分辨率的图片在读入到内存后的内存占用是十分可观的,因此在点击缩略图进入原图浏览后,由于要左右滑动来查看其它图片的原图,因此至少加载三张原图(不考虑边缘情况),分别是当前查看的图片和与之相邻的图片,而其他图片则先加载缩略图,在滚动到那些图片时才去加载原图以及与之相邻的原图,并且替换远处的原图为缩略图。

  • 滚动优化
    在滚动完全结束后才去加载原图并替换缩略图,以防止滚动时卡顿。

  • 同时支持本地与网络图片
    通过URL的类型来判断图片是否来自网络,如果来自网络则异步下载并显示进度,同时进行缓存。

  • 原图浏览时支持常见的手势
    原图浏览器时支持单击隐藏和显示导航栏和工具条,双击在适应屏幕和原始尺寸之间切换,捏和手势可以缩放图片,左右滑动可以切换图片。

  • 支持批量导出与删除照片
    可以通过工具栏进入编辑模式来批量处理图片的导出与删除。

技术要点

概述

照片浏览器框架依赖了SDWebImage和MBProgressHUD,前者用于处理图片的异步下载与缓存,后者用于显示图片下载的进度。用于缩略图显示的是collectionView,查看原图时每一张图片都被均匀排列在scrollView上,每一张图片也被包裹了一个scrollView用于处理缩放。

block数据源

使用代理模式回调数据源会使得代码较为分散,因此本框架使用了block来回调,在SGPhotoBrowser中有四个数据源block,通过实现他们并且提供相应的数据即可完成图片显示,这四个block如下面代码所示。

@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourceNumberBlock numberOfPhotosHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourcePhotoBlock photoAtIndexHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserReloadRequestBlock reloadHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserDeletePhotoAtIndexBlock deleteHandler;

每个照片通过一个SGPhotoModel数据模型类要描述,其中包含了photoURL与thumbURL,分别代表原图和缩略图的URL,通过URL是否是fileURL来决定是否要异步下载缓存。
block数据源在缩略图浏览时被collectionView的dataSource所调用,在原图浏览时被调用以获取特定位置的图片URL或进行删除照片后的数据刷新。

内存优化

在查看原图时,加载当前位置和与其相邻位置的原图,其他位置均加载缩略图,在滑动过程中,动态的切换原图的加载位置并将原来位置的原图替换为缩略图,以保证内存中最多有三张原图被加载以节省内存,具体实现代码如下。

// 点击index处的缩略图时调用,来显示原图
- (void)loadImageAtIndex:(NSInteger)index {// 通过browser的数据源方法获取模型数量NSInteger count = self.browser.numberOfPhotosHandler();// 遍历所有照片模型以及照片视图for (NSInteger i = 0; i < count; i++) {SGPhotoModel *model = self.browser.photoAtIndexHandler(i);SGZoomingImageView *imageView = self.imageViews[i];NSURL *photoURL = model.photoURL;NSURL *thumbURL = model.thumbURL;// index位置和与其相邻的位置加载原图if (i >= index - 1 && i <= index + 1) {if (imageView.isOrigin) continue;// 根据URL选择图片是直接从本地加载还是异步下载缓存的方法[imageView.innerImageView sg_setImageWithURL:photoURL model:model];// 用于指示这个imageView是否加载的是原图imageView.isOrigin = YES;// 缩放至适应屏幕[imageView scaleToFitAnimated:NO];} else {// 对于其他位置的图片,如果是原图,则替换为缩略图if (!imageView.isOrigin) continue;[imageView.innerImageView sg_setImageWithURL:thumbURL model:model];imageView.isOrigin = NO;[imageView scaleToFitAnimated:NO];}}
}

滚动优化

在scrollView的滚动效果尚未停止时进行耗时操作会造成卡顿,为了避免这种情况,可以在scrollView减速完毕后再进行耗时操作。在本框架中,在左右滑动切换图片时,如果立即加载原图,会造成卡顿,因此在scrollView减速完毕后才将缩略图替换为原图,具体实现如下。

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {// 先通过偏移量计算出当前滚动到的图片的索引CGFloat offsetX = scrollView.contentOffset.x;NSInteger index = (offsetX + _pageW * 0.5f) / _pageW;// 索引发生变化时才更新并加载原图if (_index != index) {_index = index;// 上文提到的加载原图的方法[self loadImageAtIndex:_index];}
}

本地图片与网络图片的处理

所有的图片都是通过URL进行设置,通过为UIImageView添加分类,并添加方法sg_setImageWithURL:model:方法,传入当前要加载的图片的URL以及照片模型,在方法内,通过URL类型来判断是否要进行异步下载和缓存,在异步下载时,使用MBProgressHUD来指示进度,具体代码如下。

@interface UIImageView (SGExtension)
// 通过动态绑定来实现为UIImageView添加属性
@property (nonatomic, weak) MBProgressHUD *hud;
@property (nonatomic, strong) SGPhotoModel *model;- (void)sg_setImageWithURL:(NSURL *)url model:(SGPhotoModel *)model;@end
@implementation UIImageView (SGExtension)
// 动态绑定hud和model两个属性的key
static char hudKey;
static char modelKey;
// 由于分类不允许添加属性,因此需要手动实现setter与getter
@dynamic hud;
@dynamic model;- (void)sg_setImageWithURL:(NSURL *)url {if (![url isFileURL]) {// 如果不是文件URL,则说明需要下载,通过SDWebImage处理SDImageCache *cache = [SDImageCache sharedImageCache];SDWebImageManager *mgr = [SDWebImageManager sharedManager];NSString *key = [mgr cacheKeyForURL:url];// 如果在缓存中找到了图片,则直接加载并返回if ([cache diskImageExistsWithKey:key] || ([cache imageFromMemoryCacheForKey:key] != nil)) {[self sd_setImageWithURL:url];return;}// 如果已经有了进度指示器,则说明正在下载图片,直接返回if (self.hud != nil) {return;}// 图片需要下载,且任务还未开始,通过MBProgressHUD指示下载进度,通过SDWebImage来下载和缓存图片MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self];self.hud = hud;hud.mode = MBProgressHUDModeAnnularDeterminate;[self addSubview:hud];[hud showAnimated:YES];// 如果对应于当前原图的缩略图已经下载完成,则先在原图浏览中显示缩略图作为占位图,否则显示默认的黑色图片。UIImage *placeHolderImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"SGPhotoBrowser.bundle/ImagePlaceholder.png" ofType:nil]];if (self.model.thumbURL) {NSString *key = [mgr cacheKeyForURL:self.model.thumbURL];UIImage *tempImage = [cache imageFromMemoryCacheForKey:key];if (tempImage == nil) {tempImage = [cache imageFromDiskCacheForKey:key];}if (tempImage) {placeHolderImage = tempImage;}}[self sd_setImageWithURL:url placeholderImage:placeHolderImage options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {hud.progress = (float)receivedSize / expectedSize;} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {[hud removeFromSuperview];self.hud = nil;}];} else {// 对于文件URL,直接从文件系统中加载self.image = [UIImage imageWithContentsOfFile:url.path];}
}
// 公共方法,由于占位图相关逻辑需要缩略图URL,因此需要传递model,上面的方法为私有方法
- (void)sg_setImageWithURL:(NSURL *)url model:(SGPhotoModel *)model {self.model = model;[self sg_setImageWithURL:url];
}
// 动态绑定的两属性的getter和setter
#pragma mark - Setter
- (void)setHud:(MBProgressHUD *)hud {objc_setAssociatedObject(self, &hudKey, hud, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (void)setModel:(SGPhotoModel *)model {objc_setAssociatedObject(self, &modelKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}#pragma mark - Getter
- (MBProgressHUD *)hud {return objc_getAssociatedObject(self, &hudKey);
}- (SGPhotoModel *)model {return objc_getAssociatedObject(self, &modelKey);
}
@end

原图浏览时的手势处理

每张图片使用一个scrollView包裹来处理捏合手势缩放,同时通过touchesEnded::方法来判断单击和双击,由于双击时会经过单击状态,这里将单击事件滞后0.2s处理,如果在这期间触发了双击,则取消单击事件的处理,实现如下。

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {UITouch *touch = [touches anyObject];CGPoint touchPt = [touch locationInView:self.innerImageView];self.currentTouchPoint = touchPt;NSInteger tapCount = touch.tapCount;switch (tapCount) {case 1:// 延时执行,防止和双击事件重叠[self performSelector:@selector(handleSingleTap) withObject:nil afterDelay:0.2];break;case 2:[self handleDoubleTap];break;default:break;}[[self nextResponder] touchesEnded:touches withEvent:event];
}- (void)handleDoubleTap {// 取消单击事件[NSObject cancelPreviousPerformRequestsWithTarget:self];// 在适应屏幕和原始尺寸之间翻转图片的显示状态[self toggleStateAnimated:YES];
}

图片的批量处理

在照片的数据模型SGPhotoModel上有一个isSelected属性来判断当前图片是否被选中,通过collectionView的代理方法didUnhighlightItemAtIndexPath:来处理图片的选中与反选,为了统一点击事件,将点击缩略图进入原图浏览模式的代码也放到了这里,通过是否是编辑模式来区分,编辑模式由于和工具栏直接相关,因此被记录在工具栏中,具体实现代码如下。

- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath {SGPhotoCell *cell = (SGPhotoCell *)[collectionView cellForItemAtIndexPath:indexPath];// 如果处于编辑模式,则处理图片的选中和反选并返回if (self.toolBar.isEditing) {SGPhotoModel *model = self.photoAtIndexHandler(indexPath.row);model.isSelected = !model.isSelected;// 记录所有选中的图片数据模型if (model.isSelected) {[self.selectModels addObject:model];} else {[self.selectModels removeObject:model];}cell.model = model;return;}// 如果缩略图在下载中,则不允许进入原图浏览,hud用于指示下载进度,因此有hud则正在下载if (cell.imageView.hud) return;// 如果缩略图已经下载完毕,则允许进入原图浏览模式SGPhotoViewController *vc = [SGPhotoViewController new];vc.browser = self;vc.index = indexPath.row;[self.navigationController pushViewController:vc animated:YES];
}

更多技术细节可以在GitHub上的源码中查看,点击这里前去GitHub下载源码,欢迎Star和指出不足。

转载于:https://www.cnblogs.com/aiwz/p/6153999.html

iOS开源照片浏览器框架SGPhotoBrowser的设计与实现相关推荐

  1. C++ miniblink mb开源浏览器框架

    桌面浏览器开发,之前一直用的是qt自带的webkit模板,存在一些刷新问题,升级后mingw版本不在支持webkits,只得寻求三方控件. miniblink 是一款基于chromium内核开源的浏览 ...

  2. iOS开源加密相册Agony的实现(四)

    简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...

  3. iOS开源框架和项目总结

    github上关于iOS的各种开源项目集合(转) .entry-header UI 下拉刷新 EGOTableViewPullRefresh - 最早的下拉刷新控件. SVPullToRefresh  ...

  4. iOS开源框架及项目大全(一定有你想要的,后期在不断进行分类方便大家查阅)

    图像: 1.图片浏览控件MWPhotoBrowser  实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作. ...

  5. 移动周刊第 178 期:iOS 开源框架、项目和学习资料汇总

    写在前面 本期移动周刊第 178 期如约而至,聚焦 Android.iOS.VR/AR/MR.直播等前沿移动开发技术,收录一周最热点,解读开发技巧,我们希望从中能够让你有一些收获,如果你有好的文章以及 ...

  6. 字节跳动开源 Go HTTP 框架 Hertz 设计实践

    01前言 Hertz 是字节跳动服务框架团队研发的超大规模的企业级微服务 HTTP 框架,具有高易用性.易扩展.低时延等特点.在经过了字节跳动内部一年多的使用和迭代后,如今已在 CloudWeGo 正 ...

  7. 最火的iOS开源项目

    1. AFNetworking 支持HTTP请求和基于REST的网络服务(包括GET.POST.PUT.DELETE等): 支持ARC: 要求iOS 5.0及以上版本: 有一些插件扩展已有的功能,还有 ...

  8. iOS开源资源汇总(完整项目,三方,博客,视频)长期更新

    下边都学会就大神了: 声明:都是网上搜集的,能标明出处的都标了.别只搜集而不看,与君共勉.. 先看完整项目完整App@HackerNews-React-Native用 React Native 完成的 ...

  9. iOS开发库和框架大全

    音频 AudioBus:下一代 App 到 App 的实时音频路由.官网 AudioKit:一个强大的音频合成,处理和分析的工具集.官网 EZAudio:一个基于 Core Audio 的 iOS/O ...

最新文章

  1. 塞尔达amiibo_塞尔达荒野之息pC版(附带全Amiibo)安装教程,最无敌的游戏
  2. 参加技术会议的一些小窍门
  3. 阅读《深入理解程序设计使用linux汇编语言》
  4. java二嗨租车项目_Java入门第二季6-1租车项目代码
  5. 涨姿势 | 一文读懂备受大厂青睐的ClickHouse高性能列存核心原理
  6. 酱油和gbt酱油哪个好_酱油越贵越好?认准瓶身这4处,轻松挑到好酱油!
  7. Lucene学习笔记
  8. spring zipkin mysql_springboot + zipkin + mysql
  9. mysql grep 提取错误日志_通过grep 获取MySQL错误日志信息的方法
  10. XSS绕过与防御总结
  11. WKWebView详解
  12. 微软制作win7启动U盘的工具
  13. Java中基本数据类型的转换
  14. 深度学习:GAN案例练习-minst手写数字
  15. 前后端分离实现文件下载功能
  16. Qt界面刷新优化的一些心得
  17. 详谈软件工程之软件测试与维护
  18. uniapp使用uni-ui插件的方式
  19. Mysql workbench画ER图
  20. PS制作华丽的紫色立体字

热门文章

  1. Java并发编程之ThreadGroup
  2. Java5线程并发库之LOCK(锁)CONDITION(条件)实现线程同步通信
  3. 【leetcode】104. Maximum Depth of Binary Tree
  4. jQuery动态设置样式List item
  5. 搭建nginx流媒体服务器(支持HLS)
  6. svn添加用户.sh
  7. 远程用power shell 管理vmware view 池用户
  8. golang学习之旅(2)- go的数据基本数据类型及变量定义方式
  9. python标准库os中的方法_python中OS常用方法
  10. Linux常用命令全网最全