iOS开源照片浏览器框架SGPhotoBrowser的设计与实现
简介
近日在制作一个开源加密相册时附带着设计了一个照片浏览器,在进一步优化后发布到了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和指出不足。
iOS开源照片浏览器框架SGPhotoBrowser的设计与实现的更多相关文章
- iOS开源项目、框架资源
总结的 iOS.Mac开源项目.库.知识点:http://www.open-open.com/lib/view/open1442664670352.html
- iOS开源加密相册Agony的实现(四)
简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...
- iOS - 开源框架、项目和学习资料汇总(其他篇)
数据库 1. FMDB – sqlite的工具, 多线程FMDatabaseQueue实例,FMDB数据库的使用演示和封装工具类.GXDatabaseUtils – 在FMDB基础上的工具.2. re ...
- IOS 使用SDWebImage实现仿新浪微博照片浏览器
使用第三方库SDWebImage实现仿新浪微博照片浏览器,可以下载图片缓存,点击之后滚动查看相片,具体效果如下: 代码如下: WeiboImageView.h: #import <UIKit/U ...
- 直接拿来用!最火的iOS开源项目
1. AFNetworking 在众多iOS开源项目中,AFNetworking可以称得上是最受开发者欢迎的库项目.AFNetworking是一个轻量级的iOS.Mac OS X网络通信类库,现在是G ...
- 直接拿来用!最火的iOS开源项目(一~三)
结束了GitHub平台上“最受欢迎的Android开源项目”系列盘点之后,我们正式迎来了“GitHub上最受欢迎的iOS开源项目”系列盘点.今天,我们将介绍20个在GitHub上非常受开发者欢迎的iO ...
- iOS -- 开源项目和库
TimLiu-iOS 目录 UI 下拉刷新 模糊效果 AutoLayout 富文本 图表 表相关与Tabbar 隐藏与显示 HUD与Toast 对话框 其他UI 动画 侧滑与右滑返回手势 gif动画 ...
- iOS 开源项目
在 Github 上 Star 太多了,有时候很难找到自己想要的开源库,所以在此记录下来.便于自己开发使用,也顺便分享给大家. 动画 awesome-ios-animation收集了iOS平台下比较主 ...
- (转)直接拿来用!最火的iOS开源项目(一)
1. AFNetworking 在众多iOS开源项目中,AFNetworking可以称得上是最受开发者欢迎的库项目.AFNetworking是一个轻量级的iOS.Mac OS X网络通信类库,现在是G ...
随机推荐
- Oracle:常用的一些基本操作
表操作 查看系统中当前用户所有表: select * from user_tables;select * from user_indexs;select * from user_triggers; s ...
- logback生成多个不同的日志文件
用logback生成日志文件做日志分析,日志写到多个文件中 http://stackoverflow.com/questions/2488558/logback-to-log-different-me ...
- java 面向对象编程。。。。
经过一周的学习(java),总结了许多,对java的理解,java的类型,运用,安装,基础语法,变量,常量,表达式,语句 java从C语言中继承了大量语言特性.java面向对象编程的基本特征,包括继承 ...
- kubernetes controller 实现
对于kubernetes中不存在的资源类型,我们可以通过自定义资源的方式进行扩展,首先创建customresourcedefinition对象定义资源及其schema,然后就可以创建自定义的资源了,但 ...
- python3全栈开发-socket编程
一. 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务端为你提供视 ...
- bootStrap Table 如何使用
最近在使用bootStrap Table 的表格功能有一些自己的理解写下来分享一下主要用的是一个bootStrapTable 和 jquery 的混合开发 具体怎样引入bootStrap Table ...
- MySQL InnoDB 索引原理
本文由 网易云发布. 作者:范鹏程,网易考拉海购 InnoDB是 MySQL最常用的存储引擎,了解InnoDB存储引擎的索引对于日常工作有很大的益处,索引的存在便是为了加速数据库行记录的检索.以下是 ...
- 基于webpack的React项目搭建(二)
前言 前面我们已经搭建了基础环境,现在将开发环境更完善一些. devtool 在开发的过程,我们会经常调试,so,为了方便我们在chrome中调试源代码,需要更改webpack.config.js,然 ...
- [NOI 2011]道路修建
Description 在 W 星球上有 n 个国家.为了各自国家的经济发展,他们决定在各个国家 之间建设双向道路使得国家之间连通.但是每个国家的国王都很吝啬,他们只愿 意修建恰好 n – 1条双向道 ...
- ●BZOJ 3994 [SDOI2015]约数个数和
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3994 题解: 莫比乌斯反演 (先定义这样一个符号[x],如果x为true,则[x]=1,否则 ...