前言
在上一篇里已经将相册分类列表界面完成了,乍一看是不是跟系统相册很像呢。那这一篇我们就来把这个多选图片选择器的基本功能做完。
开始
数据源
照片展示界面的数据是由之前的相册分类列表界面传来的,所以切换至ASAlbumListController,实现UITableViewDelegate
下的tableView:didSelectRowAtIndexPath:
方法
#pragma mark -- UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ASPhotoGridController *gridViewController = [[ASPhotoGridController alloc] init];
PHFetchResult *fetchResult = self.sectionFetchResults[indexPath.section];
if (indexPath.section == 0) {
gridViewController.assetsFetchResults = fetchResult;
} else {
// 获取选择行的PHAssetCollection
NSArray *sections = self.sectionFetchResults[indexPath.section];
if (!sections || sections.count < indexPath.row) return;
PHCollection *collection = sections[indexPath.row];
if (![collection isKindOfClass:[PHAssetCollection class]]) return;
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
gridViewController.assetsFetchResults = assetsFetchResult;
}
[self.navigationController pushViewController:gridViewController animated:YES];
}
回到照片展示界面
毋庸置疑照片展示界面的主体就是一个UICollectionView
,并且使用流布局排布。首先我们先自定义UICollectionViewCell
来展示图片。
需要传入图片,以及资源重用标识。资源重用标识的作用是在请求图片到图片后匹配Cell。
选中状态添加了maskLayer
,实现简单蒙板效果,maskLayer
的contents
里存放选中的效果图片,可自定义。
@interface ASPhotoGridCell : UICollectionViewCell
@property (nonatomic, strong) UIImage *thumbnailImage;
@property (nonatomic, copy) NSString *representedAssetIdentifier;
@property (strong, nonatomic) UIImageView *imageView;
@property (strong, nonatomic) CALayer *maskLayer;
@end
@implementation ASPhotoGridCell
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self.contentView.layer addSublayer:self.maskLayer];
} else {
[self.maskLayer removeFromSuperlayer];
}
}
- (void)setThumbnailImage:(UIImage *)thumbnailImage {
_thumbnailImage = thumbnailImage;
self.imageView.image = thumbnailImage;
}
- (UIImageView *)imageView {
if (!_imageView) {
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
_imageView.backgroundColor = [UIColor redColor];
[self.contentView addSubview:_imageView];
}
return _imageView;
}
- (CALayer *)maskLayer {
if (!_maskLayer) {
_maskLayer = [CALayer layer];
_maskLayer.frame = self.bounds;
_maskLayer.contents = (id)[UIImage imageNamed:@"mask"].CGImage;
_maskLayer.backgroundColor = [UIColor colorWithRed:1.f green:1.f blue:1.f alpha:.2f].CGColor;
}
return _maskLayer;
}
@end
同样地
控制器可以使用UICollectionViewController,但这里小编就用UIViewController代替了,延展性高一些。
添加协议,定义常量
@interface ASPhotoGridController () <PHPhotoLibraryChangeObserver, UICollectionViewDataSource>
@property (strong, nonatomic) UICollectionView *collectionView;
@end
static NSString * const CellReuseIdentifier = @"Cell";
static CGSize AssetGridThumbnailSize;
懒加载初始化UICollectionView
#pragma mark - getters and setters
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumInteritemSpacing = 1;
layout.minimumLineSpacing = 1;
CGFloat itemWidth = (CGRectGetWidth([UIScreen mainScreen].bounds) - 4 + 1) / 4;
layout.itemSize = CGSizeMake(itemWidth, itemWidth);
_collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor whiteColor];
//支持多选
_collectionView.allowsMultipleSelection = YES;
[_collectionView registerClass:[ASPhotoGridCell class] forCellWithReuseIdentifier:CellReuseIdentifier];
_collectionView.dataSource = self;
}
return _collectionView;
}
界面布局
根据流布局获取Cell
的尺寸,初始化常量AssetGridThumbnailSize
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self customPageViews];
CGFloat scale = [UIScreen mainScreen].scale;
CGSize cellSize = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize;
AssetGridThumbnailSize = CGSizeMake(cellSize.width * scale, cellSize.height * scale);
}
- (void)customPageViews {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"select" style:UIBarButtonItemStylePlain target:self action:@selector(e__confirmImagePickerAction)];
[self.view addSubview:self.collectionView];
}
实现UICollectionView
数据源代理
其中的图片数据是每次都请求的,非常耗资源,不是很合理,下一篇我们会细讲缓存预加载策略。
#pragma mark -- UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.assetsFetchResults.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PHAsset *asset = self.assetsFetchResults[indexPath.item];
ASPhotoGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellReuseIdentifier forIndexPath:indexPath];
cell.representedAssetIdentifier = asset.localIdentifier;
// 请求图片
[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeDefault
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
//判断当前cell的资源是否是当前获取的资源
if ([cell.representedAssetIdentifier isEqualToString:asset.localIdentifier]) {
cell.thumbnailImage = result;
}
}];
return cell;
}
到此,可以先运行起来看看了~
接下来我们得完善下功能
监测相册资源变化
- 注册观察者
//注册观察相册变化的观察者
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
- 图片资源一旦有变化就会调用
photoLibraryDidChange:
,所以我们需要实现此方法,对相册变化做出反应
#pragma mark -- PHPhotoLibraryChangeObserver
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
// 检测是否有资源变化
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges == nil) {
return;
}
// 通知在后台队列,重定位到主队列进行界面更新
dispatch_async(dispatch_get_main_queue(), ^{
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
[collectionView reloadData];
} else {
// 如果相册有变化,collectionview动画增删改
[collectionView performBatchUpdates:^{
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
if ([removedIndexes count] > 0) {
[collectionView deleteItemsAtIndexPaths:[self indexPathsFromIndexes:removedIndexes section:0]];
}
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
if ([insertedIndexes count] > 0) {
[collectionView insertItemsAtIndexPaths:[self indexPathsFromIndexes:insertedIndexes section:0]];
}
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count] > 0) {
[collectionView reloadItemsAtIndexPaths:[self indexPathsFromIndexes:changedIndexes section:0]];
}
} completion:NULL];
}
});
}
#pragma mark - public method
- (NSArray *)indexPathsFromIndexes:(NSIndexSet *)indexSet section:(NSUInteger)section {
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:indexSet.count];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
}];
return indexPaths;
}
- 销毁观察者
//销毁观察相册变化的观察者
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
最后
不能忘本哟,还得获取所选的图片资源,这里小编使用的是Delegate
,大家也能选择Block
或者Notification
。
请求图片资源时有两种方法,一种直接获取固定尺寸的UIImage
,还有一种是获取图片数据,这里获取的是后者。
@protocol ASImagePickerControllerDelegate <NSObject>
@optional
- (void)as_didFinishPickingImageData:(NSArray *)imageDatas;
@end
//////////////////////////////////////////////////////////////////////////////
#pragma mark - event response(e__method)
- (void)e__confirmImagePickerAction {
NSMutableArray *imageDatas = [NSMutableArray array];
//获取已选状态的Items的IndexPath
NSArray *selectedAssets = [self assetsAtIndexPaths:[self.collectionView indexPathsForSelectedItems]];
__block NSInteger requestCompletedIndex = 0;
for (PHAsset *asset in selectedAssets) {
//请求图片资源
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
if (imageData) [imageDatas addObject:imageData];
requestCompletedIndex++;
if (requestCompletedIndex > selectedAssets.count - 1) {
if ([self.delegate respondsToSelector:@selector(as_didFinishPickingImageData:)]) {
[self.delegate as_didFinishPickingImageData:imageDatas];
}
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
}];
}
}
//根据indexPath获取相应的资源
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths {
if (indexPaths.count == 0) { return nil; }
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
PHAsset *asset = self.assetsFetchResults[indexPath.item];
[assets addObject:asset];
}
return assets;
}
在每个界面进行delegate
的传递,最终在弹出ASImagePickerController
的控制器内实现代理以供显示
@interface ViewController ()<ASImagePickerControllerDelegate, UINavigationControllerDelegate>
- (IBAction)chooseAssets:(id)sender {
ASImagePickerController *imagePicker = [[ASImagePickerController alloc] init];
imagePicker.delegate = self;
[self presentViewController:imagePicker animated:YES completion:nil];
}
- (void)as_didFinishPickingImageData:(NSArray *)imageDatas {
//已选图片的显示
}
到现在为止,一个多选功能的图片选择器就基本完成了。代码基本都贴出来了,可以和提供的Demo对照起来看。
效果图
结束语
轮毂终于出来了,但可能还有点不平整,滚起来不够顺吧,下一篇我们会来加工一下,讲一下Photos API里提到的预加载策略,这个含金量高一些。有兴趣的码友们持续关注噢~
ASImagePicker也在持续更新中...
https://github.com/alanshen0118/ASImagePicker
文章中有任何错误希望读者能积极指出,我会及时更正。
如果喜欢,请持续关注,顺便点个喜欢噢👇👇👇帮五菱加加油~@_@