iOS 自定义图片选择器 1 - PhotoKit

【 写在前面:笔者按照Instagram的图片选取器写了个小Demo,
该系列文章为笔者实现Demo的步骤,若有不正确的地方还望指出来,共同学习。
地址在最后】


iOS开发者对于图片选择器不会感到陌生,例如最常用的UIImagePicker就是系统提供的,可以实现简单的图片选取与编辑,当需要高度定制化的时候,就需要我们自己造轮子了。笔者就以Instagram为参考参考来造自己轮子。

当然,也能找到很多优秀的第三方,如:
MWPhotoBrowser:(https://github.com/mwaterfall/MWPhotoBrowser)
它是一个图片选取的框架,需要传入图片资源。我们的业务很多时候是直接取的系统的相册,而笔者造的轮子定位就是一个基于系统相册的自定义图片选择器。所以我们必须先了解下如何获取系统的图片资源。

在iOS 8以前想要与系统相册进行互动使用的是ALAsset,而在iOS9开始,苹果就不再推荐了。从iOS 8开始,苹果推出PhotoKit来取代ALAsset,PhotoKit还能够配合ICloud,且当下基本以iOS 8为最低版本,我们也就直接使用PhotoKit来进行开发。

使用选取器的基本的流程是(调用选取器->获取图片->展示图片->选取图片->回调并关闭),在实际流程中会有细节上的差异。

下面笔者会根据功能的先后,把主要使用到的方法依次列举出来。

1.权限的配置与获取

在iOS 10中需要添加额外的安全设定,否则程序会直接崩溃,我们用到了相册,所以需要在info.plist里添加NSPhotoLibraryUsageDescription的key值,其value为获取权限的描述,该描述会在设置中的相册权限展示。
要获取图片还要知道获取图片的权限是否开启,可调用以下方法:

[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
    /*当status等于PHAuthorizationStatusAuthorized表示用户已经授予了权限。
    该方法在应用初次询问时,会直接弹出系统的权限询问,
    用户操作后才会回调,所以不存在PHAuthorizationStatusNotDetermined
    (用户还未对权限进行选择)的状态。*/
}];

//当然,也可以使用下方的方法直接获取权限的状态
//PHAuthorizationStatus state = [PHPhotoLibrary authorizationStatus];

2.获取相册信息

在用到PhotoKit的地方需要包含其头文件

#import 

获取手机内所有的相册:

    //search collection data
    PHFetchResult * sysfetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum
                                                                              subtype:PHAssetCollectionSubtypeAlbumRegular
                                                                              options:nil];

遍历相册,获取需要的值,如相册的Title:

for (PHAssetCollection * assetCollection in sysfetchResult) {
     NSString * collectionTitle = assetCollection.localizedTitle;
}

获取相册所有图片的信息

/**get PHAsset from collection*/
- (NSArray *)getAssetWithCollection:(PHAssetCollection *)collection {
    //set fetchoptions
    PHFetchOptions * options = [[PHFetchOptions alloc] init];
    options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate"
                                                              ascending:YES]];
    
    //search
    NSMutableArray * assetArray = [NSMutableArray array];
    PHFetchResult * assetFetchResult = [PHAsset fetchAssetsInAssetCollection:collection
                                                                     options:options];
    
    for (PHAsset * asset in assetFetchResult) {
        [assetArray addObject:asset];
        
    }
    return assetArray;
}

3.获取图片

在PhotoKit中,单个图片的信息都保存在PHAsset中,也可以说每一个PHAsset就是一个图片的所有数据,例如宽、高、图片地址等等。
我们得通过PHAsset来获取图片

    PHImageRequestOptions * options = [[PHImageRequestOptions alloc] init];
    
    //图片的质量相关设置
    options.resizeMode = PHImageRequestOptionsResizeModeNone;
    options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
    
    //当图片为iCloud资源时,是否通过网络获取,默认为NO
    options.networkAccessAllowed = YES;
    
    //是否为同步的,默认为NO (这里笔者需要将其顺序加入集合,所以改为了YES)
    options.synchronous = YES;
    
    [options setProgressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
//        [weakself requestProgress:progress];
    }];
    _imagesArray = [NSMutableArray array];
    for (PHAsset * asset in _currentCollectionData) {
        //这个方法的回调是在主线程中回调的
        [[PHImageManager defaultManager] requestImageForAsset:asset
                                                   targetSize:CGSizeMake(targetSize, targetSize)
                                                  contentMode:PHImageContentModeAspectFill
                                                      options:options
                                                resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
            [_imagesArray addObject:result];
        }];
    }

这里要特别说明一下targetSize,targetSize是你指定获取图片的大小,一定要按需设置,一般情况下照片都是很大的比如2000*3000,而屏幕的宽度只有375,图片的获取会占用系统资源。当同一时间大批量获取数据时(例如一个相册1000张图片),会耗费较多的时间,可能引发性能问题。

这里稍微展开下笔者的性能的优化问题

场景:相册有2000张图片,以collectionView进行展示,每行四个图片,以6s为例。整屏状态下同时会展示30个图片左右,当极速滑动时,不能有卡顿,且图片展示不发生错误。

笔者尝试过如下两种方法:

1.及时加载图片
2.统一加载图片

刚开始笔者用的第一种方法在Cell中进行加载,所有一切都很正常,但是当滑动稍快时,会有明显卡顿。遂采取了异步加载的方式,卡顿的确是解决了,可图片的展示又发生了问题(当图片获取到时,这时的Cell已经是另一个图片了)。再后来又加上了判断是否为当前图片,经过多次改进,还是会在极速滑动时能感觉到卡顿,陷入了困境。
代码越来越臃肿,越来越复杂,效果却差强人意。我打开了几个巨头的APP,他们的图片加载仿佛是进入选取界面的时候就已经加载好了,连iCloud的图片都没有显示加载的过程。当时觉得只有提前统一加载好了才能达到这种效果,总觉得不科学,直接加载上千张图片性能肯定会有问题。但笔者在第一条路已经绕晕了,也就尝试了下第二种方法,除了量特别大的情况下(数千张时),会有明显的加载过程,其他的时候感觉还不错(心里还是痒痒,有过来人还请不吝赐教)。

顺便列出一个方法,是将一个相册的图片预加载的,理论上可以提升加载图片的速度,具体效果未测:

    //load image to cache
    PHCachingImageManager * manager = [[PHCachingImageManager alloc] init];
    [manager stopCachingImagesForAllAssets];
    [manager startCachingImagesForAssets:_currentCollectionData
                              targetSize:CGSizeMake(targetSize, targetSize)
                             contentMode:PHImageContentModeAspectFill
                                 options:nil];

4 其他常用方法

取消指定的图片请求:

    [[PHImageManager defaultManager] cancelImageRequest:_requestID];

//_requestID为请求图片时的返回,如:
//- (PHImageRequestID)requestImageForAsset:targetSize:contentMode:options:resultHandler:

获取图片的详细信息

    PHContentEditingInputRequestOptions * editOptions = [[PHContentEditingInputRequestOptions alloc] init];
    editOptions.networkAccessAllowed = NO;
    PHContentEditingInputRequestID editID = [self requestContentEditingInputWithOptions:editOptions completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
        //是否为iCloud资源
        //BOOL isCloud = [info[PHContentEditingInputResultIsInCloudKey] boolValue];
    }];

Demo: PJPhotoPicker

GitHub: https://github.com/BigBigPo/RJPhotoPicker
弱弱的问一句···这GIF怎么循环播放···

singleChoose.gif

mutableChoose.gif

你可能感兴趣的:(iOS 自定义图片选择器 1 - PhotoKit)