美文网首页iOS程序猿iOS Developer互联网科技
PhotoKit制作相册或选择器(三):照片展示

PhotoKit制作相册或选择器(三):照片展示

作者: Alan_Sim | 来源:发表于2016-09-13 14:09 被阅读424次

前言

上一篇里已经将相册分类列表界面完成了,乍一看是不是跟系统相册很像呢。那这一篇我们就来把这个多选图片选择器的基本功能做完。

开始

数据源

照片展示界面的数据是由之前的相册分类列表界面传来的,所以切换至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,实现简单蒙板效果,maskLayercontents里存放选中的效果图片,可自定义

@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对照起来看。

效果图
照片展示2.gif

结束语

轮毂终于出来了,但可能还有点不平整,滚起来不够顺吧,下一篇我们会来加工一下,讲一下Photos API里提到的预加载策略,这个含金量高一些。有兴趣的码友们持续关注噢~

ASImagePicker也在持续更新中...
https://github.com/alanshen0118/ASImagePicker

文章中有任何错误希望读者能积极指出,我会及时更正。
如果喜欢,请持续关注,顺便点个喜欢噢👇👇👇帮五菱加加油~@_@

Thanks!!!

相关文章

网友评论

    本文标题:PhotoKit制作相册或选择器(三):照片展示

    本文链接:https://www.haomeiwen.com/subject/wxmpettx.html