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

前言

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

开始

数据源

照片展示界面的数据是由之前的相册分类列表界面传来的,所以切换至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!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,825评论 25 707
  • 在初秋的时光里, 寻着梦的足迹走在现实的生活里, 我听见花落的声音,看见叶子的凋零。 伴着彷徨的心情, 向往着, ...
    酾藇阅读 270评论 0 2
  • 1.化解我金錢關系障礙的途徑是 放下姿態,越過圍欄,主動去找她 2.當我如圖所提示的,在現實中我可以做些什麽 主動...
    秀ShirleyZ阅读 225评论 0 0
  • 阅读原文 在UIImage上面绘制内容,步骤 得到图片 UIImage* image=[UIImage image...
    学生陈希阅读 371评论 0 0