一.工程链接
通过定义两套协议一层一层的传递数据,也就是一套协议传递一层(供参考,没有完全完成)
链接:
https://pan.baidu.com/s/1qXL0J1CTgcarJeZKmXE_Bw 密码:ioo8
完整工程
链接:
https://pan.baidu.com/s/1f4XGluebTlOYAOX668b4bQ 密码:5xj6
二.开发流程
1.预期的功能
读取程序内部保存的图片文件(一个图片对应一个文件)
使 UITableView展示
点击Edit 进入编辑状态 选择需要操作的图片
点击Delete删除选中的图片
点击Done完成操作
2.为了减少内存的消耗,保存图片需要保存两套,一套小图,一套原图
示意文件关系
3.定义一个类来管理文件的相关操作FileOperation,单例创建
单例创建
static FileOperation *instance = nil;
#pragma mark -------单例创建 ---------
//高级做法
+(FileOperation *)sharedOperation{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [FileOperation new];
});
return instance;
}
封装路径方法
#pragma mark -------获取documents的路径 ---------
-(NSString *)documentPath{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
}
#pragma mark -------获取缩略图的路径 ---------
-(NSString *)thumbnailPath{
return [[self documentPath] stringByAppendingPathComponent:@"small"];
}
#pragma mark -------获取原图的路径 ---------
-(NSString *)originalPath{
return [[self documentPath] stringByAppendingPathComponent:@"org"];
}
#pragma mark ------- 拼接路径 获取缩略图的文件路径 ---------
-(NSString *)thumbnailPathWithName:(NSString *)name{
return [[self documentPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"small/%@",name]];
}
#pragma mark -------拼接路径 获取缩略图的文件路径 ---------
-(NSString *)originalPathWithName:(NSString *)name{
return [[self documentPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"org/%@",name]];
}
根据路径创建文件并将图片保存其中
#pragma mark -------创建文件夹 ---------
-(void)creatDirectoryWithName:(NSString *)name{
NSString *path = [[self documentPath] stringByAppendingPathComponent:name];
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
#pragma mark -------保存图片数组 ---------
-(void)saveImages:(NSArray *)imagesArray{
//首先保存外部传来的数据,防止操作过程中对象被释放或者篡改
NSArray *images = [imagesArray copy];
//创建org和small目录
[self creatDirectoryWithName:@"org"];
[self creatDirectoryWithName:@"small"];
//将所有的图片保存
for (UIImage *img in images) {
[self saveImage:img];
}
}
#pragma mark ------获得图片名字路径并保存 ---------
-(void)saveImage:(UIImage *)img{
//将图片裁剪为100×100
UIImage *smallImage = [img scaleToSize:CGSizeMake(100, 100)];
//得到唯一的名字 可以用时间作为文件的名字
//NSString *name = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
NSString *name = [NSString stringWithFormat:@"%@",[[NSUUID UUID] UUIDString]];
NSLog(@"%@",name);
//Documents/org/图片的名字
NSString *orgPath = [self originalPathWithName:name];
//保存这张图片
[self saveImage:img filePath:orgPath];
//Documents/small/图片的名字
NSString *smallPath = [self thumbnailPathWithName:name];
//保存这张图片
[self saveImage:smallImage filePath:smallPath];
}
#pragma mark -------通过路径保存单个图片 ---------
-(void)saveImage:(UIImage *)img filePath:(NSString *)path{
//1.判断文件是否存在
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(@"文件已存在");
return;
}
//2.创建文件
NSData *data = UIImagePNGRepresentation(img);
BOOL result = [[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil];
if (result == YES) {
NSLog(@"创建成功");
}else{
NSLog(@"创建失败");
}
}
读取图片和删除图片
#pragma mark -------读取文件内容 缩略图 ---------
-(NSArray *)loadData{
//存放图片
NSMutableArray *images = [NSMutableArray array];
//1.获取缩略图路径
NSString *thumbPath = [self thumbnailPath];
//2.读取目录里面的所有内容 文件的名称组成的数组
NSArray *fileNamesArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:thumbPath error:nil];
//3.使用for循环读取所有文件的内容
for (NSString *name in fileNamesArray) {
//1.拼接文件的完整路径
NSString *filePath = [thumbPath stringByAppendingPathComponent:name];
//2.读取文件内容
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
//3.转化图片
UIImage *img = [UIImage imageWithData:imageData];
//4.创建图片对应的模型
PhotoViewModel *model = [PhotoViewModel new];
model.image = img;
model.index = [fileNamesArray indexOfObject:name];
model.isSelected = NO;//默认状态,可以不写
model.name = name;
//4.添加到数组
[images addObject:model];
}
return images;
}
#pragma mark -------读取缩略图对应的原图 ---------
-(UIImage *)originalImageWithName:(NSString *)name{
//得到路径
NSString *orgPath = [self originalPathWithName:name];
//获取二进制数据
NSData *data = [NSData dataWithContentsOfFile:orgPath];
//二进制数据转化为图片
UIImage *img = [UIImage imageWithData:data];
return img;
}
#pragma mark -------删除某个图片文件 ---------
-(void)removeImage:(NSString *)name{
//获取small路径
NSString *thumPath = [self thumbnailPathWithName:name];
//获取原图路径
NSString *orgPath = [self originalPathWithName:name];
//删除两个文件
[[NSFileManager defaultManager] removeItemAtPath:thumPath error:nil];
[[NSFileManager defaultManager] removeItemAtPath:orgPath error:nil];
}
4.显示的图片的数据封装为模型
/**模型的图片*/
@property (nonatomic,strong) UIImage *image;
/**索引值-第几张图片*/
@property (nonatomic,assign) NSInteger index;
/**图片的状态 默认就是0*/
@property (nonatomic,assign) BOOL isSelected;
/**图片对应的文件名*/
@property (nonatomic,copy) NSString *name;
5.将图片写入到文件中,只执行一次
//只做一次
self.imgsArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
NSString *name = [NSString stringWithFormat:@"%d",i+1];
UIImage *img = [UIImage imageNamed:name];
[self.imgsArray addObject:img];
}
NSLog(@"%ld",self.imgsArray.count);
[[FileOperation sharedOperation] saveImages:self.imgsArray];
6.读取数据,并配置相关信息
//读取数据
self.imagesArray = [NSMutableArray arrayWithArray:[[FileOperation sharedOperation] loadData]];
//注册
[self.tableView registerClass:[PhotoCell class] forCellReuseIdentifier:@"cellID"];
#pragma mark -------UITableView ---------
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
//每行显示四个
NSInteger row = self.imagesArray.count/4;
if (_imagesArray.count%4 != 0) {
row++;
}
return row;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"];
//判断是不是最后一行
NSInteger totol = [tableView numberOfRowsInSection:0];
//正常四个
NSRange range = NSMakeRange(indexPath.row*4, 4);
//只有最后一行可能不是四个
if (indexPath.row == totol-1) {
//最后一行
range.length = _imagesArray.count-indexPath.row*4;
}
//传数据
cell.indexPath = indexPath;//必须先传,否则重写setImages有误
cell.images = [_imagesArray subarrayWithRange:range];
//样式
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return kHeight+kPadding*2;
}
7.自定义自己想要的cell
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
//创建四个photoView
for (int i = 0; i < 4; i++) {
//创建
PhotoView *pv = [[NSBundle mainBundle] loadNibNamed:@"PhotoView" owner:nil options:nil].lastObject;
//设置tag值
pv.tag = i+1;
//隐藏
pv.hidden = YES;
//添加到cell
[self.contentView addSubview:pv];
pv.backgroundColor = [UIColor redColor];
}
}
return self;
}
-(void)layoutSubviews{
for (int i = 1; i <= 4; i++) {
PhotoView *pv = [self viewWithTag:i];
pv.frame = CGRectMake(kPadding+(i-1)*(kPadding+kWidth), kPadding, kWidth, kHeight);
}
}
-(void)setImages:(NSArray *)images{
_images = images;
//显示数据
for (int i = 0; i < 4; i++) {
PhotoView *pv = [self viewWithTag:i+1];
if (i < _images.count) {
pv.hidden = NO;
PhotoViewModel *model = images[I];
model.index = _indexPath.row*4+I;
pv.model = model;
}else{
pv.hidden = YES;
}
}
}
8.通过Xib完成Cell里面的视图的定义
Xib里面的内容
一个UIButton和一个UIImageView组成,UIBUtton用来显示我们的图片,UIImageView用来显示删除信息,并且在Xib文件里面就将其hidden设为YES
通过消息通知在PhotoView和ViewController之间传递数据
定义一套协议用来将点击的事件传递给viewController
#import <Foundation/Foundation.h>
#import "Constants.h"
//用单独的一个类来管理协议 降低耦合性
@protocol PhotoViewDelegate <NSObject>
//定义一套代理 方法
//1.第几个被选中了
//2.状态 选中 取消 正常
-(void)photoViewStatusChanged:(PhotoViewStatus)status Index:(NSInteger)index;
@end
PhotoView的配置
-(void)awakeFromNib{
[super awakeFromNib];
//注册
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeToEdit) name:kPhotoViewChangeStatusToEditNotificationName object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeToNormal) name:kPhotoViewChangeStatusToNormalNotificationName object:nil];
}
-(void)dealloc{
//移除
[[NSNotificationCenter defaultCenter] removeObserver:self];
// [NSNotificationCenter defaultCenter] removeObserver:<#(nonnull id)#> name:<#(nullable NSNotificationName)#> object:<#(nullable id)#>
}
-(void)changeToEdit{
_checkImageView.hidden = NO;
}
-(void)changeToNormal{
_checkImageView.hidden = YES;
_checkImageView.image = [UIImage imageNamed:@"CheckBox_Normal"];
}
-(void)setModel:(PhotoViewModel *)model{
_model = model;
//显示图片
[_imageButton setImage:model.image forState:UIControlStateNormal];
//判断当前view的状态
if (model.isSelected == YES) {
_checkImageView.image = [UIImage imageNamed:@"CheckBox_Selected"];
}else{
_checkImageView.image = [UIImage imageNamed:@"CheckBox_Normal"];
}
}
- (IBAction)imageBtnDidClick:(UIButton *)sender {
//定义一个变量 记录状态
PhotoViewStatus status;
if (_checkImageView.hidden == YES) {
//正常状态 放大图片
status = PhotoViewStatusNormal;
}else{
if (_model.isSelected == NO) {
//选中状态
status = PhotoViewStatusSelected;
_checkImageView.image = [UIImage imageNamed:@"CheckBox_Selected"];
//_checkImageView.tag = 1;
}else{
//取消选中
status = PhotoViewStatusDeselected;
_checkImageView.image = [UIImage imageNamed:@"CheckBox_Normal"];
//_checkImageView.tag = 0;
}
}
//将点击的事件传递给viewController
//1.找到主界面
ViewController *vc = (ViewController *)[UIApplication sharedApplication].keyWindow.rootViewController;
//2.传递事件
if ([vc respondsToSelector:@selector(photoViewStatusChanged:Index:)]) {
[vc photoViewStatusChanged:status Index:_model.index];
}
}
ViewControll实现代理的方法
#pragma mark -------PhotoViewDelegate ---------
-(void)photoViewStatusChanged:(PhotoViewStatus)status Index:(NSInteger)index{
//获取index对应的数据模型
PhotoViewModel *model = [self.imagesArray objectAtIndex:index];
switch (status) {
case PhotoViewStatusNormal:
//放大浏览图片
[BrowserView showBrowserViewAtIndex:index AndFrame:self.view.bounds];
break;
case PhotoViewStatusSelected:
//选中图片
[self.selectedArray addObject:model];
self.deleteBtn.enabled = YES;
//更改对当前模型的状态
model.isSelected = YES;
break;
case PhotoViewStatusDeselected:
//取消选中
[self.selectedArray removeObject:model];
if (self.selectedArray.count == 0) {
self.deleteBtn.enabled = NO;
}
//更改对当前模型的状态
model.isSelected = NO;
break;
default:
break;
}
}
9.多个地方需要用到的变量
#ifndef Constants_h
#define Constants_h
#define kPadding 10
#define kWidth (([UIScreen mainScreen].bounds.size.width-5*kPadding)/4.0)
#define kHeight (([UIScreen mainScreen].bounds.size.width-5*kPadding)/4.0)
#define kPhotoViewChangeStatusToEditNotificationName @"kPhotoViewChangeStatusToEditNotificationName"
#define kPhotoViewChangeStatusToNormalNotificationName @"kPhotoViewChangeStatusToNormalNotificationName"
//定义一个枚举记录PhotoView的状态
typedef NS_ENUM(NSUInteger,PhotoViewStatus) {
PhotoViewStatusSelected, //选中
PhotoViewStatusDeselected, //取消选中
PhotoViewStatusNormal //浏览图片
};
#endif /* Constants_h */
10.自定义一个用于管理图片的类BrowserView
提供一个类方法创建
//创建浏览视图 并且显示出来
+(BrowserView *)showBrowserViewAtIndex:(NSInteger)index AndFrame:(CGRect)frame{
BrowserView *bv = [[BrowserView alloc] initWithFrame:frame];
//接受产生的索引值,加载默认的视图
bv.index = index;
//用于测试的背景颜色
bv.backgroundColor = [UIColor blackColor];
//显示
[[UIApplication sharedApplication].keyWindow addSubview:bv];
//渐变动画
bv.alpha = 0.5;
[UIView animateWithDuration:0.5 animations:^{
bv.alpha = 1;
}];
return bv;
}
//重写initWithFrame方法 布局
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//用于滚动的视图
self.scrollView = [ScrollView new];
_scrollView.bounces = NO;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.contentSize = CGSizeMake(3*kScrollWidth, kScrollHeight);
_scrollView.pagingEnabled = YES;
_scrollView.delegate = self;
[self addSubview:_scrollView];
//创建用于展示的三个容器视图
self.leftView = [self creatImageView];
self.middleView = [self creatImageView];
self.rightView = [self creatImageView];
}
return self;
}
//创建图片容器视图
-(UIImageView *)creatImageView{
UIImageView *imgView = [UIImageView new];
imgView.contentMode = UIViewContentModeScaleAspectFit;
[self.scrollView addSubview:imgView];
return imgView;
}
//重新布局 重写initwithframe的时候不应该给ferame
-(void)layoutSubviews{
[super layoutSubviews];
//计算尺寸
_scrollView.frame = CGRectMake(0, 0, kScrollWidth, kScrollHeight);
}
#pragma mark -------移除视图 ---------
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//消失
//得到browserView的对象
[self removeFromSuperview];
}
重写index的set方法,数据一来就刷新显示的内容
//重写index的set方法,一有数据来就重新刷新视图
-(void)setIndex:(NSInteger)index{
_index = index;
//获取图片数据
_middleView.image = [[DataCenter defaultCenter] imageAtIndex:index];
_leftView.image = [[DataCenter defaultCenter] imageAtIndex:index-1];
_rightView.image = [[DataCenter defaultCenter] imageAtIndex:index+1];
//判断该如何显示
if (_leftView.image == nil) {
//第一张
_middleView.frame = CGRectMake(0, 0, kScrollWidth, kScrollHeight);
_rightView.frame = CGRectMake(kScrollWidth, 0, kScrollWidth, kScrollHeight);
_scrollView.contentSize = CGSizeMake(2*kScrollWidth, 0);
}else if(_rightView.image == nil){
//最后一张
_middleView.frame = CGRectMake(kScrollWidth, 0, kScrollWidth, kScrollHeight);
_leftView.frame = CGRectMake(0, 0, kScrollWidth, kScrollHeight);
_scrollView.contentSize = CGSizeMake(2*kScrollWidth, 0);
}else{
//中间
_leftView.frame = CGRectMake(0, 0, kScrollWidth, kScrollHeight);
_middleView.frame = CGRectMake(kScrollWidth, 0, kScrollWidth, kScrollHeight);
_rightView.frame = CGRectMake(kScrollWidth*2, 0, kScrollWidth, kScrollHeight);
_scrollView.contentSize = CGSizeMake(3*kScrollWidth, 0);
}
//始终显示中间的视图
[_scrollView setContentOffset:CGPointMake(_middleView.frame.origin.x, 0) animated:NO];
//重新设置偏移值
_preOffsetX = _scrollView.contentOffset.x;
}
//拖拽结束做的事情
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//比较前后偏移值判断滑动方向
if (scrollView.contentOffset.x > _preOffsetX) {
//右边
if (_rightView.image != nil) {
self.index = _index+1;
}
}else if(scrollView.contentOffset.x < _preOffsetX){
//左边
if (_leftView.image != nil) {
self.index = _index-1;
}
}
//没有滑动
}
移除视图的时候[self removeFromSuperview],这里的self是盖上去的视图,我们是要把这个盖上去的视图从其父视图上移除,重写一个类继承于UIScrollView传递点击事件(虽然UIScrollView上面放置了三个UIImageView,但是UIImageView的交互事件默认是关闭的)
#import "ScrollView.h"
@implementation ScrollView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//将点击事件传递给父视图
[self.superview touchesBegan:touches withEvent:event];
}
@end
降低代码冗余,将数据的加载抽出来作为一个类
#import "DataCenter.h"
#import "FileOperation.h"
#import "PhotoViewModel.h"
static DataCenter *instance = nil;
@implementation DataCenter
+(DataCenter *)defaultCenter{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [DataCenter new];
});
return instance;
}
//通过懒加载读取数据
-(NSMutableArray *)dataArray{
if (_dataArray == nil) {
self.dataArray = [NSMutableArray arrayWithArray:[[FileOperation sharedOperation] loadData]];
}
return _dataArray;
}
-(UIImage *)imageAtIndex:(NSInteger)index{
if (index < 0 || index >= self.dataArray.count) {
return nil;
}
//根据model取相应的图片
PhotoViewModel *model = self.dataArray[index];
//返回图片
return [[FileOperation sharedOperation] originalImageWithName:model.name];
}
@end
三.运行结果


