前言
在上一篇里已經(jīng)將相冊(cè)分類列表界面完成了,乍一看是不是跟系統(tǒng)相冊(cè)很像呢。那這一篇我們就來把這個(gè)多選圖片選擇器的基本功能做完。
開始
數(shù)據(jù)源
照片展示界面的數(shù)據(jù)是由之前的相冊(cè)分類列表界面?zhèn)鱽淼?,所以切換至ASAlbumListController,實(shí)現(xiàn)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];
}
回到照片展示界面
毋庸置疑照片展示界面的主體就是一個(gè)UICollectionView,并且使用流布局排布。首先我們先自定義UICollectionViewCell來展示圖片。
需要傳入圖片,以及資源重用標(biāo)識(shí)。資源重用標(biāo)識(shí)的作用是在請(qǐng)求圖片到圖片后匹配Cell。
選中狀態(tài)添加了maskLayer,實(shí)現(xiàn)簡單蒙板效果,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代替了,延展性高一些。
添加協(xié)議,定義常量
@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;
}
界面布局
根據(jù)流布局獲取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];
}
實(shí)現(xiàn)UICollectionView數(shù)據(jù)源代理
其中的圖片數(shù)據(jù)是每次都請(qǐng)求的,非常耗資源,不是很合理,下一篇我們會(huì)細(xì)講緩存預(yù)加載策略。
#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;
// 請(qǐng)求圖片
[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeDefault
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
//判斷當(dāng)前cell的資源是否是當(dāng)前獲取的資源
if ([cell.representedAssetIdentifier isEqualToString:asset.localIdentifier]) {
cell.thumbnailImage = result;
}
}];
return cell;
}
到此,可以先運(yùn)行起來看看了~
接下來我們得完善下功能
監(jiān)測(cè)相冊(cè)資源變化
- 注冊(cè)觀察者
//注冊(cè)觀察相冊(cè)變化的觀察者
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
- 圖片資源一旦有變化就會(huì)調(diào)用
photoLibraryDidChange:,所以我們需要實(shí)現(xiàn)此方法,對(duì)相冊(cè)變化做出反應(yīng)
#pragma mark -- PHPhotoLibraryChangeObserver
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
// 檢測(cè)是否有資源變化
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges == nil) {
return;
}
// 通知在后臺(tái)隊(duì)列,重定位到主隊(duì)列進(jìn)行界面更新
dispatch_async(dispatch_get_main_queue(), ^{
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
[collectionView reloadData];
} else {
// 如果相冊(cè)有變化,collectionview動(dòng)畫增刪改
[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;
}
- 銷毀觀察者
//銷毀觀察相冊(cè)變化的觀察者
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
最后
不能忘本喲,還得獲取所選的圖片資源,這里小編使用的是Delegate,大家也能選擇Block或者Notification。
請(qǐng)求圖片資源時(shí)有兩種方法,一種直接獲取固定尺寸的UIImage,還有一種是獲取圖片數(shù)據(jù),這里獲取的是后者。
@protocol ASImagePickerControllerDelegate <NSObject>
@optional
- (void)as_didFinishPickingImageData:(NSArray *)imageDatas;
@end
//////////////////////////////////////////////////////////////////////////////
#pragma mark - event response(e__method)
- (void)e__confirmImagePickerAction {
NSMutableArray *imageDatas = [NSMutableArray array];
//獲取已選狀態(tài)的Items的IndexPath
NSArray *selectedAssets = [self assetsAtIndexPaths:[self.collectionView indexPathsForSelectedItems]];
__block NSInteger requestCompletedIndex = 0;
for (PHAsset *asset in selectedAssets) {
//請(qǐng)求圖片資源
[[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];
}
}];
}
}
//根據(jù)indexPath獲取相應(yīng)的資源
- (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;
}
在每個(gè)界面進(jìn)行delegate的傳遞,最終在彈出ASImagePickerController的控制器內(nèi)實(shí)現(xiàn)代理以供顯示
@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 {
//已選圖片的顯示
}
到現(xiàn)在為止,一個(gè)多選功能的圖片選擇器就基本完成了。代碼基本都貼出來了,可以和提供的Demo對(duì)照起來看。
效果圖

結(jié)束語
輪轂終于出來了,但可能還有點(diǎn)不平整,滾起來不夠順吧,下一篇我們會(huì)來加工一下,講一下Photos API里提到的預(yù)加載策略,這個(gè)含金量高一些。有興趣的碼友們持續(xù)關(guān)注噢~
ASImagePicker也在持續(xù)更新中...
https://github.com/alanshen0118/ASImagePicker
文章中有任何錯(cuò)誤希望讀者能積極指出,我會(huì)及時(shí)更正。
如果喜歡,請(qǐng)持續(xù)關(guān)注,順便點(diǎn)個(gè)喜歡噢??????幫五菱加加油~@_@