简介

虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制。本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目)、WiFi传图、照片文件加密等功能。目前项目和文章会同时前进,项目的源代码可以在github上下载。

点击前往GitHub

概述

上一篇文章主要介绍了相册管理界面的设计与实现。本文主要介绍图片浏览器设计的技术细节。

图片浏览器设计

说明

之前尝试了使用MWPhotoBrowser来处理多图浏览与查看原图,但有些地方不尽人意,遂自己做了一个图片浏览器,为了以后将其做成框架,没有将其耦合到工程里,而是作为一个框架跟随工程一起开发的,目前已经实现了原图延迟加载、内存优化、批量删除与保存等功能,该框架使用block来回调数据源方法,使用十分方便,目前仍在开发中,源码在GitHub提供的工程目录下Libs/SGPhtoBrowser可以找到。

本文主要介绍其实现细节,限于篇幅只能介绍一部分,其余部分将在接下来的文章中一一介绍。

交互界面设计及说明

以下几幅图片展示了相册的缩略图展示、编辑与原图查看功能。

缩略图查看


编辑模式


查看原图

在原图查看页面单击可以隐藏导航栏和工具栏,双击切换原图与适应屏幕的缩放状态,同时支持捏和手势的缩放。为了优化内存,原图查看时当前图片以及左右两侧的图片都加载了原图,较远处的图片加载的是缩略图,当左右滑动到缩略图时,会去加载当前以及相邻的原图,并且将远处的所有原图替换为缩略图。

图片浏览器的总体设计

数据源模型设计

图片浏览器的缩略图浏览界面为collectionView,collectionView需要的数据为缩略图,而原图浏览时需要的是原图,因此图片浏览器的所需要的数据模型应该包含原图地址与缩略图地址,除此之外,为了标记照片的选中状态,模型中还应该有一个字段用于记录是否选中。综上所述,模型设计如下。

@interface SGPhotoModel : NSObject

@property (nonatomic, copy) NSURL *photoURL;
@property (nonatomic, copy) NSURL *thumbURL;
@property (nonatomic, assign) BOOL isSelected; @end

数据源回调设计

常规的数据源回调都是通过代理方式,考虑到代理方式写起来比较麻烦,代码也比较分散,这里使用了block回调。数据源主要包含了三个方法,前两个分别是去请求数据模型的数量和获取特定位置的数据模型,第三个请求重新加载数据,之所以存在第三个回调,是因为照片浏览器可能会删除一些图片,但他们没有权限去操作模型数组(只能获取特定位置的模型),因此需要请求数据源去刷新模型数据。具体设计如下。

typedef SGPhotoModel * (^SGPhotoBrowserDataSourcePhotoBlock)(NSInteger index);
typedef NSInteger (^SGPhotoBrowserDataSourceNumberBlock)(void);
typedef void(^SGPhotoBrowserReloadRequestBlock)(void);
@interface SGPhotoBrowser : UIViewController @property (nonatomic, copy, readonly) SGPhotoBrowserDataSourceNumberBlock numberOfPhotosHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourcePhotoBlock photoAtIndexHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserReloadRequestBlock reloadHandler; - (void)setNumberOfPhotosHandlerBlock:(SGPhotoBrowserDataSourceNumberBlock)handler;
- (void)setphotoAtIndexHandlerBlock:(SGPhotoBrowserDataSourcePhotoBlock)handler;
- (void)setReloadHandlerBlock:(SGPhotoBrowserReloadRequestBlock)handler; @end

之所以设置成为readonly并手动提供setter,是因为系统提供的setter无法生成block的智能补全。

对外接口设计

为了方便使用浏览器,只需要继承SGPhotoBrowser并实现数据源的block并提供数据源需要的数据模型即可,因此不需要多少额外的接口,但为了方便用户自定义,提供了一个property来设置每行展示的照片数,并且提供了reloadData方法,当模型数据变化时,要求照片浏览器重新加载模型刷新数据,综上所述,照片浏览器的完整设计如下。

typedef SGPhotoModel * (^SGPhotoBrowserDataSourcePhotoBlock)(NSInteger index);
typedef NSInteger (^SGPhotoBrowserDataSourceNumberBlock)(void);
typedef void(^SGPhotoBrowserReloadRequestBlock)(void); @interface SGPhotoBrowser : UIViewController @property (nonatomic, assign) NSInteger numberOfPhotosPerRow;
@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourceNumberBlock numberOfPhotosHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourcePhotoBlock photoAtIndexHandler;
@property (nonatomic, copy, readonly) SGPhotoBrowserReloadRequestBlock reloadHandler; - (void)setNumberOfPhotosHandlerBlock:(SGPhotoBrowserDataSourceNumberBlock)handler;
- (void)setphotoAtIndexHandlerBlock:(SGPhotoBrowserDataSourcePhotoBlock)handler;
- (void)setReloadHandlerBlock:(SGPhotoBrowserReloadRequestBlock)handler;
- (void)reloadData; @end

缩略图浏览实现

数据源处理

在图片浏览器中有一个collectionView用于展示所有的图片,图片浏览器本身作为其数据源和代理,collectionView对数据源的请求通过浏览器向父类(由用户继承浏览器类实现)去请求相应的block,其中numberOfItemsInSection:方法对应numberOfPhotosHandler,cellForItemAtIndexPath:方法对应photoAtIndexHandler,具体的实现如下。

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSAssert(self.numberOfPhotosHandler != nil, @"you must implement 'numberOfPhotosHandler' block to tell the browser how many photos are here");
return self.numberOfPhotosHandler();
} - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSAssert(self.photoAtIndexHandler != nil, @"you must implement 'photoAtIndexHandler' block to provide photos for the browser.");
SGPhotoModel *model = self.photoAtIndexHandler(indexPath.row);
SGPhotoCell *cell = [SGPhotoCell cellWithCollectionView:collectionView forIndexPaht:indexPath];
cell.model = model;
return cell;
}

这里通过断言来防止用户没有实现相应的block,但这又引入了一个问题,可能在用户设置block之前对numberOfItemsInSection:进行了回调,这就会报错,为了防止这个问题,collectionView的数据源和代理在block被实现之后才会被启用,具体的实现为在这两个block的setter中检查是否两个block都已经实现,只有都实现了,才对collectionView的代理和数据源赋值,具体实现如下。

- (void)checkImplementation {
if (self.photoAtIndexHandler && self.numberOfPhotosHandler) {
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView reloadData];
}
} - (void)setphotoAtIndexHandlerBlock:(SGPhotoBrowserDataSourcePhotoBlock)handler {
_photoAtIndexHandler = handler;
[self checkImplementation];
} - (void)setNumberOfPhotosHandlerBlock:(SGPhotoBrowserDataSourceNumberBlock)handler {
_numberOfPhotosHandler = handler;
[self checkImplementation];
}

排布尺寸处理

为了保证照片以极小的间隔紧密排布,需要根据屏幕尺寸严格计算每个Cell的尺寸并通过collectionView的代理方法提供,尺寸的计算说明图如下。

根据上图,设屏幕宽度为width,每行的照片数量为n,则每个Cell的宽高=(width - (n - 1) * gutt - 2 * margin) / n。

具体的尺寸计算的实现如下。

// 初始化
- (void)initParams {
_margin = 0;
_gutter = 1;
self.numberOfPhotosPerRow = 3;
}
// 边距
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(_margin, _margin, _margin, _margin);
}
// Cell尺寸
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat value = (self.view.bounds.size.width - (self.numberOfPhotosPerRow - 1) * _gutter - 2 * _margin) / self.numberOfPhotosPerRow;
return CGSizeMake(value, value);
}
// Cell上下间距(行间距)
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return _gutter;
}
// Cell左右间距(列间距)
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return _gutter;
}

Cell设计

每个Cell都通过数据模型SGPhotoModel来显示数据,Cell上铺满了一个ImageView来显示缩略图,除此之外,为了处理选中,在ImageView上加一个遮盖层,默认隐藏,通过设置sg_select(为了和系统的select区分)这一属性来处理隐藏和现实。遮盖层包含了一个半透明背景和一个选中的效果图,它同样被定义在Cell的类文件中,具体实现如下。

objective-c

@interface SGPhotoCell : UICollectionViewCell

@property (nonatomic, strong) SGPhotoModel *model;

// 处理选中

@property (nonatomic, assign) BOOL sg_select;

// 处理重用和快速创建Cell

+ (instancetype)cellWithCollectionView:(UICollectionView )collectionView forIndexPaht:(NSIndexPath )indexPath;

@end

```objective-c
// 遮盖层视图的定义
@interface SGPhotoCellMaskView : UIView
// 遮盖层图片
@property (nonatomic, weak) UIImageView *selectImageView; @end @implementation SGPhotoCellMaskView - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:0.6f];
self.hidden = YES;
UIImage *selectImage = [UIImage imageNamed:@"SelectButton"];
UIImageView *selectImageView = [[UIImageView alloc] initWithImage:selectImage];
self.selectImageView = selectImageView;
[self addSubview:selectImageView];
}
return self;
}
// 遮盖层图片在右下角显示
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat padding = 8;
CGFloat selectWH = 28;
CGFloat selectX = self.bounds.size.width - padding - selectWH;
CGFloat selectY = self.bounds.size.height - padding - selectWH;
self.selectImageView.frame = CGRectMake(selectX, selectY, selectWH, selectWH);
} @end
// Cell的实现
@interface SGPhotoCell ()
// 包含缩略图显示的ImageView与选中的遮盖层
@property (nonatomic, weak) UIImageView *imageView;
@property (nonatomic, weak) SGPhotoCellMaskView *selectMaskView; @end @implementation SGPhotoCell + (instancetype)cellWithCollectionView:(UICollectionView *)collectionView forIndexPaht:(NSIndexPath *)indexPath {
static NSString *ID = @"SGPhotoCell";
[collectionView registerClass:[SGPhotoCell class] forCellWithReuseIdentifier:ID];
SGPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
return cell;
} - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
UIImageView *imageView = [UIImageView new];
// 使得缩略图显示适当的部分
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
self.imageView = imageView;
[self.contentView addSubview:imageView];
// 添加遮盖层
SGPhotoCellMaskView *selectMaskView = [[SGPhotoCellMaskView alloc] initWithFrame:self.contentView.bounds];
[self.contentView addSubview:selectMaskView];
self.selectMaskView = selectMaskView;
}
return self;
} - (void)setModel:(SGPhotoModel *)model {
_model = model;
NSURL *thumbURL = model.thumbURL;
if ([thumbURL isFileURL]) {
self.imageView.image = [UIImage imageWithContentsOfFile:thumbURL.path];
} else {
[self.imageView sd_setImageWithURL:thumbURL];
}
// 设置模型时根据模型设置选中状态
self.sg_select = model.isSelected;
}
// 通过选中属性的setter来处理遮盖层的显示与隐藏
- (void)setSg_select:(BOOL)sg_select {
_sg_select = sg_select;
self.selectMaskView.hidden = !_sg_select;
} - (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = self.contentView.bounds;
} @end

关于选中的具体逻辑将在下一篇文章介绍。

照片浏览器的使用

上文主要介绍了照片浏览器的缩略图浏览界面的具体设计,这里将介绍如何使用该浏览器。

1.继承照片浏览器类SGPhotoBrowser

@interface SGPhotoBrowserViewController : SGPhotoBrowser
// 用于存储当前用户相册文件系统的根目录,在前面的文章中有介绍
@property (nonatomic, copy) NSString *rootPath; @end

2.使用一个数组来保存所有的数据模型,数据模型通过沙盒中特定用户的文件系统去加载。

@interface SGPhotoBrowserViewController ()

@property (nonatomic, strong) NSArray<SGPhotoModel *> *photoModels;

@end

3.实现数据源的三个block。

- (void)commonInit {
// 设置每行显示的照片数
self.numberOfPhotosPerRow = 4;
// 根据根目录去获取文件夹名称(用/分割路径字符串,并取最后一个部分),作为控制器标题
self.title = [SGFileUtil getFileNameFromPath:self.rootPath];
// 用于添加图片的按钮,后续的文章会介绍
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addClick)];
WS(); // 创建weakSelf的宏,防止循环引用
// 实现图片浏览器的数据源block
[self setNumberOfPhotosHandlerBlock:^NSInteger{
return weakSelf.photoModels.count;
}];
[self setphotoAtIndexHandlerBlock:^SGPhotoModel *(NSInteger index) {
return weakSelf.photoModels[index];
}];
[self setReloadHandlerBlock:^{
[weakSelf loadFiles];
}];
}

4.实现加载数据模型的方法

- (void)loadFiles {
NSFileManager *mgr = [NSFileManager defaultManager];
// 在每个相册文件夹内,有原图文件夹Photo和缩略图文件夹Thumb,分别获取路径
NSString *photoPath = [SGFileUtil photoPathForRootPath:self.rootPath];
NSString *thumbPath = [SGFileUtil thumbPathForRootPath:self.rootPath];
NSMutableArray *photoModels = @[].mutableCopy;
// 扫描Photo文件夹,获取所有原图文件的名称
NSArray *fileNames = [mgr contentsOfDirectoryAtPath:photoPath error:nil];
for (NSUInteger i = 0; i < fileNames.count; i++) {
NSString *fileName = fileNames[i];
// 原图与缩略图同名,因此可以同时拼接出原图和缩略图路径
// 使用URL是为了框架后期能够兼容网络图片
NSURL *photoURL = [NSURL fileURLWithPath:[photoPath stringByAppendingPathComponent:fileName]];
NSURL *thumbURL = [NSURL fileURLWithPath:[thumbPath stringByAppendingPathComponent:fileName]];
// 每个模型都包含了一张图片的原图与缩略图路径
SGPhotoModel *model = [SGPhotoModel new];
model.photoURL = photoURL;
model.thumbURL = thumbURL;
[photoModels addObject:model];
}
self.photoModels = photoModels;
// 调用父类的reloadData方法要求collectionView重新加载数据
[self reloadData];
}

总结

本文主要介绍了图片浏览器的缩略图展示部分的设计,项目的下载地址可以在文首找到。下一篇文章将会介绍图片的选取批处理方法以及查看原图的一些细节,欢迎关注项目后续。

iOS开源加密相册Agony的实现(四)的更多相关文章

  1. iOS开源加密相册Agony的实现(七)

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

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

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

  3. iOS开源加密相册Agony的实现(二)

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

  4. iOS开源加密相册Agony的实现(一)

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

  5. iOS开源加密相册Agony的实现(六)

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

  6. iOS开源加密相册Agony的实现(五)

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

  7. iOS开源照片浏览器框架SGPhotoBrowser的设计与实现

    简介 近日在制作一个开源加密相册时附带着设计了一个照片浏览器,在进一步优化后发布到了GitHub供大家使用,该框架虽然没有MWPhotoBrowser那么强大,但是使用起来更为方便,操作更符合常规相册 ...

  8. iOS -- 开源项目和库

    TimLiu-iOS 目录 UI 下拉刷新 模糊效果 AutoLayout 富文本 图表 表相关与Tabbar 隐藏与显示 HUD与Toast 对话框 其他UI 动画 侧滑与右滑返回手势 gif动画 ...

  9. iOS开源项目周报0105

    由OpenDigg 出品的iOS开源项目周报第四期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. He ...

随机推荐

  1. hdu3342-判断有向图中是否存在(至少)3元环或回路-拓扑排序

    一:题目大意:   给你一个关系图,判断是否合法,    每个人都有师父和徒弟,可以有很多个:  不合法:  1) . 互为师徒:(有回路)  2) .你的师父是你徒弟的徒弟,或者说你的徒弟是你师父的 ...

  2. Http协议消息报头

    哎.不知道怎么写Http协议... 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议. HTTP基于TCP/IP通信协议来传递数据 ...

  3. jQuery系列 第五章 jQuery框架动画特效

    第五章 jQuery框架动画特效 5.1 jQuery动画特效说明 jQuery框架中为我们封装了众多的动画和特效方法,只需要调用对应的动画方法传递合适的参数,就能够方便的实现一些炫酷的效果,而且jQ ...

  4. [bzoj 1293] [SCOI2009] 生日礼物

    传送门(bzoj) 传送门(luogu) 题目: Description 小西有一条很长的彩带,彩带上挂着各式各样的彩珠.已知彩珠有N个,分为K种.简单的说,可以将彩带考虑为x轴,每一个彩珠有一个对应 ...

  5. HTTP响应状态解析

    100 客户端应当继续发送请求.这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝.客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应.服务器必须在请求完成后向客 ...

  6. 音频降噪算法 附完整C代码

    降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据 ...

  7. [BZOJ 4403]序列统计

    Description 给定三个正整数N.L和R,统计长度在1到N之间,元素大小都在L到R之间的单调不降序列的数量.输出答案对10^6+3取模的结果. Input 输入第一行包含一个整数T,表示数据组 ...

  8. [HNOI2001]矩阵乘积

    题目描述 输入输出格式 输入格式: 第1行为:x y (第1行为两个正整数:x,y分别表示输出结果所在的行和列) 第2行为:m n o p(第2行给出的正整数表明A为m×n矩阵,B为n×o矩阵,C为o ...

  9. CSAPP-链接

    主要任务: 1.符号解析 在声明变量和函数之后,所有的符号声明都被保存到符号表. 而符号解析阶段会给每个符号一个定义. 2.重定位: 把每个符号的定义与一个内存位置关联起来,然后修改所有对这些符号的引 ...

  10. ●POJ 1113 Wall

    题链: http://poj.org/problem?id=1113 题解: 计算几何,凸包 题意:修一圈围墙把给出的点包围起来,且被包围的点距离围墙的距离不能小于L,求围墙最短为多少. 答案其实就是 ...