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和指出不足。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/470342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python公式计算器_Python-计算器

#计算器开发需求##1、实现加减乘除及拓号优先级解析#2、用户输入 1 - 2 * ( (60-30 (-40/5) * (9-2*5/3 7 /3*99/4*2998 10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式后&#xff0c;#必须自己解析里面的(),,-,*,/符号和公式&#xff0c;运算后得出结果&#xff0c;结果必须…

网络流-SAP算法模板

node 表示当前讨论到的节点&#xff1b; flow 表示该节点被传入/要传出的流量&#xff1b;s, t, ver 分别是起点&#xff0c;终点&#xff0c;节点总数&#xff1b; SAP 函数返回增广路成功增大的流量&#xff1b; dlt 是当前已传出的流量&#xff1b; c 是残量。 1 const int …

python bp神经网络分类预测结果图_深度学习入门(四)BP神经网络——数字分类...

采用的是python中机器学习库sklearn图片数据sklearn简要介绍&#xff1a;#sklearn是Python中的一个机器学习包from sklearn.datasets import load_digits #载入Sklearnimport pylab as pldigitsload_digits()#载入数据集(都是数字图片)print(digits.data.shape)#一共有1797张图…

Koa框架

一、认识Koa 前面我们已经学习了express&#xff0c;另外一个非常流行的Node Web服务器框架就是Koa。 Koa官方的介绍&#xff1a; koa&#xff1a;next generation web framework for node.js&#xff1b;koa&#xff1a;node.js的下一代web框架&#xff1b; 事实上&#x…

SPOJ GSS2 Can you answer these queries II (线段树离线) - xgtao -

Can you answer these queries II 这是一道线段树的题目&#xff0c;维护历史版本&#xff0c;给出N(<100000)个数字(-100000<x<100000),要求求出在[l,r]区间里面的连续序列的最大值&#xff0c;并且重复的数字可以加入序列但是值不能再计算。 本题明显使用线段树,它只…

Node使用MySQL

一、认识mysql2 如何可以在Node的代码中执行SQL语句来&#xff0c;这里我们可以借助于两个库&#xff1a; mysql&#xff1a;最早的Node连接MySQL的数据库驱动&#xff1b;mysql2&#xff1a;在mysql的基础之上&#xff0c;进行了很多的优化、改进&#xff1b;目前相对来说&a…

java安装后在哪里打开_冷却塔声屏障安装后降噪效果不理想,原因出在哪里?...

现有冷却塔大部分产品都不能满足噪音环保标准&#xff0c;只有少数几种低吨位超低噪声型号的冷却塔可以满足少部分区域噪声标准要求。在一些商住混合区&#xff0c;冷却塔的出风口强烈的噪声和热气往往干扰附近居民不得安宁&#xff0c;所以冷却塔的噪声问题成为环境投诉的热点…

iOS10 权限崩溃问题

iOS10 权限崩溃问题 原文: http://blog.csdn.net/runleelrg/article/details/51673025 今天 手机升级了 iOS10 Beta&#xff0c;然后用正在开发的项目 装了个ipa包&#xff0c;发现点击有关 权限访问 直接Crash了&#xff0c;并在控制台输出了一些信息&#xff1a; This app h…

Koa框架——coderhub实战

https://github.com/zep03/koa-coderhub/commits/main 一、coderhub功能接口说明 Coderhub旨在创建一个程序员分享生活动态的平台。 完整的项目接口包括&#xff1a; 面向用户的业务接口&#xff1b;面向企业或者内部的后台管理接口&#xff1b; 完成的功能如下&#xff1a;…

python 协程_Python多任务协程

协程协程&#xff0c;又称微线程&#xff0c;纤程。英文名Coroutine。协程是python个中另外一种实现多任务的方式&#xff0c;只不过比线程更小占用更小执行单元(理解为需要的资源)。为啥说它是一个执行单元&#xff0c;因为它自带CPU上下文。这样只要在合适的时机&#xff0c;…

python局域网大文件_[源码]Python简易http服务器(内网渗透大文件传输含下载命令)...

Python简易http服务器源码import SimpleHTTPServerimport SocketServerimport sysPORT 80if len(sys.argv) ! 2:print("use: web.exe port")else:PORT int(sys.argv[1])Handler SimpleHTTPServer.SimpleHTTPRequestHandlerhttpd SocketServer.TCPServer(("&…

CoderHub接口文档

CoderHub接口文档 Coderhub旨在创建一个程序员分享生活动态的平台。 完成的功能如下&#xff1a; 用户管理系统 内容管理系统 内容评论管理 内容标签管理 文件管理系统 接口完整请求示例&#xff1a;http://127.0.0.1:8000/users github仓库&#xff1a;https://github.com…

Spring 概况

网站&#xff1a;http://spring.io/ http://projects.spring.io/spring-framework Spring是一个开源框架&#xff0c;为了解决企业应用开发的复杂性而创建的&#xff0c;但现在已经不止应用于企业应用。 是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff…

属于python应用领域的有数据可视化_Python数据可视化

译者序前 言第1章 数据可视化概念框架11.1 数据、信息、知识和观点21.1.1 数据21.1.2 信息21.1.3 知识31.1.4 数据分析和观点31.2 数据转换41.2.1 数据转换为信息41.2.2 信息转换为知识71.2.3 知识转换为观点71.3 数据可视化历史81.4 可视化如何帮助决策101.4.1 …

JavaScript高级之构造函数和原型

1.1 概述 在典型的 OOP 的语言中&#xff08;如 Java&#xff09;&#xff0c;都存在类的概念&#xff0c;类就是对象的模板&#xff0c;对象就是类的实例&#xff0c;但在 ES6之前&#xff0c; JS 中并没用引入类的概念。 ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;2…

wget python3_python wget

wget -nH &#xff0d;K -E -d -np -L -r http://flatfull.com/themes/scale/-np 递归下载时不搜索上层目录-nH, –no-host-directories 不创建主机目录-d, –debug 打印调试输出-L 递归时不进入其它主机-E //将所有text/html文档以.html扩展名保存-r 递归抓取-k 抓取之后修正链…

JavaScript高级之继承

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数原型对象模拟实现继承&#xff0c;被称为组合继承。 2.1 call() 调用这个函数, 并且修改函数运行时的 this 指向 fun.call(thisArg, arg1, arg2, …) thisArg &#xff1a;当前调用函数 this 的指向对象arg1&…

【代码笔记】iOS-UILable电子表显示

一&#xff0c;效果图。 二&#xff0c;代码。 RootViewController.h #import <UIKit/UIKit.h>interface RootViewController : UIViewController {UILabel *label;NSDateFormatter *dateFormatter ; } end RootViewController.m #import "RootViewController.h&qu…

python cpu_python 让cpu满载

python 让cpu满载发布时间&#xff1a;2018-07-29 17:25:11编辑&#xff1a;admin阅读(5939)搞zabbix监控的时候&#xff0c;linux服务器的负载很低&#xff0c;如何写一个python脚本&#xff0c;让它满载呢&#xff1f;网上搜了一堆&#xff0c;发现各种不靠谱。后来终于发现了…

JavaScript高级之ES5 中的新增方法

3.1 ES5 新增方法概述 ES5 中给我们新增了一些方法&#xff0c;可以很方便的操作数组或者字符串&#xff0c;这些方法主要包括&#xff1a; 数组方法字符串方法对象方法 3.2 数组方法 迭代(遍历)方法&#xff1a;forEach()、map()、filter()、some()、every()&#xff1b; …