美文网首页iOS DeveloperiOS程序猿程序员
PhotoKit制作相册或选择器(二):相册分类

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

作者: Alan_Sim | 来源:发表于2016-09-12 14:19 被阅读277次

    前言

    上一篇我们大概了解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!!!

    相关文章

      网友评论

        本文标题:PhotoKit制作相册或选择器(二):相册分类

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