PhotoKit制作相册或选择器(二):相册分类

前言

上一篇我们大概了解Photos框架的基本知识,这一篇我们就开始动手,造一款高仿UIImagePickerController的五菱专属轮子。

相册分类

效果图

先来看一波效果,提提精神!


相册分类.gif

开始

UIImagePickerController继承的是UINavigationController,目的是点击相册分类可以Push进入照片界面,所以需要导航控制器作为基础。

@interface ASImagePickerController : UINavigationController

导航控制器的根控制器就是相册分类视图控制器(ASAlbumListController)

- (instancetype)init
{
    self = [super initWithRootViewController:[[ASAlbumListController alloc] init]];
    if (self) {
        
    }
    return self;
}

相册分类的视图控制器可以直接继承UITableViewController,这里小编就用UIViewController了。下一步导入Photos框架,这可是核心呀。

@import Photos;

@interface ASAlbumListController ()<UITableViewDataSource, UITableViewDelegate, PHPhotoLibraryChangeObserver>
//表视图,显示相册分类
@property (nonatomic, strong) UITableView *tableView;
//储存相册分类,包含allPhotos,SmartAlbum,Album
@property (nonatomic, strong) NSArray *sectionFetchResults;
//储存相册分类名
@property (nonatomic, strong) NSArray *sectionLocalizedTitles;

@end

配置数据源

#pragma mark - setup data
- (void)setupData {

    PHFetchOptions *photosOptions = [[PHFetchOptions alloc] init];
    //图片配置设置排序规则
    photosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
    
    //获取所有图片资源
    PHFetchResult *allPhotos = [PHAsset fetchAssetsWithOptions:photosOptions];
    
    //获取智能相册
    PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
    
    //获取用户自定义相册
    PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    self.sectionFetchResults = @[allPhotos, smartAlbums, topLevelUserCollections];
    self.sectionLocalizedTitles = @[@"", NSLocalizedString(@"Smart Albums", @""), NSLocalizedString(@"Albums", @"")];

}

相册缩略图预览(自定义Cell)

为了节省代码量,尽可能使用系统自带的Cell样式。UITableViewCellStyleDefault这个默认样式只有图片不符合需求,所以我就在此基础上做了些延伸

@interface ASAlbumCustomCell ()

@property (strong, nonatomic) UIImageView *frontImageView;

@property (strong, nonatomic) UIImageView *middleImageView;

@property (strong, nonatomic) UIImageView *lastImageView;

@end

这里得用懒加载,图片不够的话,对应的UIImageView也不必加载了,最多显示三张(这里偷懒了,设frame做了)
要注意的是视图的层级关系,要有一层一层的效果

#pragma mark - lazy load
- (UIImageView *)frontImageView {
    if (!_frontImageView) {
        _frontImageView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 10, 69, 69)];
        _frontImageView.contentMode = UIViewContentModeScaleAspectFill;
        _frontImageView.clipsToBounds = YES;
        [self.contentView addSubview:_frontImageView];
    }
    return _frontImageView;
}

- (UIImageView *)middleImageView {
    if (!_middleImageView) {
        _middleImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 8, 65, 65)];
        _middleImageView.contentMode = UIViewContentModeScaleAspectFill;
        _middleImageView.clipsToBounds = YES;
        [self.contentView insertSubview:_middleImageView belowSubview:self.frontImageView];
    }
    return _middleImageView;
}

- (UIImageView *)lastImageView {
    if (!_lastImageView) {
        _lastImageView = [[UIImageView alloc] initWithFrame:CGRectMake(12, 6, 61, 61)];
        _lastImageView.contentMode = UIViewContentModeScaleAspectFill;
        _lastImageView.clipsToBounds = YES;
        [self.contentView insertSubview:_lastImageView belowSubview:self.middleImageView];
    }
    return _lastImageView;
}

现在需要用这三个叠加的UIImageView来替代UITableViewCell中的UIImageView,通过填充,制造了“假象”

#pragma mark - customPageViews

- (void)customPageViews {
    
    self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    CGSize itemSize = CGSizeMake(65.f, 65.f);
    
    UIGraphicsBeginImageContext(itemSize);
    
    CGRect imageRect = CGRectMake(0.f, 0.f, itemSize.width, itemSize.height);
    
    [self.imageView.image drawInRect:imageRect];
    
    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
}

配置数据,赋值UIImageView

#pragma mark - setupData

- (void)setupData {
    for (int i = 0; i < self.thumbImages.count; i++) {
        switch (i) {
            case 0:{
                self.frontImageView.image = self.thumbImages[i];
                break;
            }
            case 1:{
                self.middleImageView.image = self.thumbImages[i];
                break;
            }
            case 2:{
                self.lastImageView.image = self.thumbImages[i];
                break;
            }
            default:
                break;
        }
    }
}

数组类型属性thumbImages在赋值后需要在ImageView上生效,所以需要写一下Setter方法

- (void)setThumbImages:(NSArray *)thumbImages {
    _thumbImages = thumbImages;
    [self setupData];
}

UITableView的数据源代理

static NSString * const AllPhotosReuseIdentifier = @"AS_AllPhotosCell";
static NSString * const CollectionCellReuseIdentifier = @"AS_CollectionCell";
static const float ListRowHeight = 89.f;

#pragma mark - system delegate
#pragma mark -- UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ASAlbumCustomCell *cell = nil;
    PHFetchResult *fetchResult = nil;
    if (indexPath.section == 0) {
        cell = [tableView dequeueReusableCellWithIdentifier:AllPhotosReuseIdentifier forIndexPath:indexPath];
        fetchResult = self.sectionFetchResults[indexPath.section];
        
        cell.textLabel.text = NSLocalizedString(@"All Photos", @"");
        
    } else {
        NSArray *collections = self.sectionFetchResults[indexPath.section];
        if (!collections || collections.count <= indexPath.row) return nil;
        PHCollection *collection = collections[indexPath.row];
        cell = [tableView dequeueReusableCellWithIdentifier:collection.localIdentifier];
        if (!cell) {
            cell = [[ASAlbumCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:collection.localIdentifier];
        }
        if ([collection isKindOfClass:[PHAssetCollection class]]) {
            fetchResult = [PHAsset fetchAssetsInAssetCollection:(PHAssetCollection *)collection options:nil];
            cell.textLabel.text = collection.localizedTitle;
        }
        //相册名
    }

    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%zi", fetchResult.count];
    //获取当前相册分类下的前三张图片
    __block NSInteger fetchImageIndex = 0;
    NSMutableArray *thumbsImages = [NSMutableArray arrayWithCapacity:3];
    for (NSInteger i = 0; i < MIN(fetchResult.count, 3); i++) {
        fetchImageIndex++;
        [[PHCachingImageManager defaultManager] requestImageForAsset:fetchResult[i] targetSize:CGSizeMake(69, 69) contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
            if (result) [thumbsImages addObject:result];
            if (--fetchImageIndex == 0) {
                cell.thumbImages = thumbsImages;
            }
        }];
    }
    
    return cell;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger numberOfRows = 0;
    //第一个section就allPhotos一行
    if (section == 0) {
        numberOfRows = 1;
    } else {
        PHFetchResult *fetchResult = self.sectionFetchResults[section];
        numberOfRows = fetchResult.count;
    }
    
    return numberOfRows;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.sectionFetchResults.count;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return self.sectionLocalizedTitles && self.sectionLocalizedTitles.count > section ? self.sectionLocalizedTitles[section] : @"";
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return ListRowHeight;
}

观察相册资源变化

Photos为开发者提供了PHPhotoLibraryChangeObserver,实现对相册资源变化的检测。
首先,先注册观察者

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];    
    //注册观察相册变化的观察者
    [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}

其次,添加协议

@interface ASAlbumListController ()<PHPhotoLibraryChangeObserver>

图片资源一旦有变化就会调用photoLibraryDidChange:,所以我们需要实现此方法,对相册变化做出反应

#pragma mark - PHPhotoLibraryChangeObserver

- (void)photoLibraryDidChange:(PHChange *)changeInstance {
    //观察者,在后台队列执行,所以刷新界面需要在主队列中
    dispatch_async(dispatch_get_main_queue(), ^{
        //深拷贝,备份比较
        NSMutableArray *updatedSectionFetchResults = [self.sectionFetchResults mutableCopy];
        __block BOOL reloadRequired = NO;
        
        [self.sectionFetchResults enumerateObjectsUsingBlock:^(PHFetchResult *collectionsFetchResult, NSUInteger index, BOOL *stop) {
            //根据原先的相片集的数据创建变化对象
            PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
            //判断变化对象是否为空,不为空则代表有相册有变化
            if (changeDetails != nil) {
                //变化后的数据替换变化前的数据
                [updatedSectionFetchResults replaceObjectAtIndex:index withObject:[changeDetails fetchResultAfterChanges]];
                reloadRequired = YES;
            }
        }];
        
        if (reloadRequired) {
            //刷新数据
            self.sectionFetchResults = updatedSectionFetchResults;
            [self.tableView reloadData];
        }
        
    });
}

最后,销毁观察者

- (void)dealloc {
    //销毁观察相册变化的观察者
    [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}

是不是很简单
现在可以运行起来,看一下效果咯~
如果有问题可以看一下小编提供的Demo

结束语

轮毂雏形出来了哟。这篇都是比较基础的用法,所以代码占了主要部分,里面就穿插了一点小编的思路。

下一篇,对应相册分类下的图片显示(包含了API中提到的缓存预加载策略)以及多选功能,先来瞄一眼效果~

照片展示.gif

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

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

Thanks!!!

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,068评论 4 62
  • 林先生和李小姐相视的那一刻他们都笑了 那天,朋友叫我出去玩,他也去了,说来也奇怪,整场聚会大概有10个人,可我偏偏...
    李佳人阅读 1,337评论 16 33
  • 很多人,被催婚,理由是结婚的人生才算完整,才会幸福。 实际情况是,没结婚,人生不完整,却自得其乐;结婚了,看似完整...
    静静的酒窝阅读 208评论 0 0
  • 这是我们共同的节日, 它的名字叫团圆。 浓郁的桂花香依旧萦绕, 芬芳的桂花酒已经酿成, 就连月亮上面, 也有一棵桂...
    时光带不走的珊珊2018阅读 473评论 9 36
  • 在这个信息爆炸的时代,我们无时无刻不在接收各种信息,这种碎片化的信息以五花八门的方式吸引着我们的注意力,这就意味着...
    萌萌视觉笔记阅读 859评论 1 9