【 写在前面:笔者按照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 <Photos/Photos.h>
获取手机内所有的相册:
//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怎么循环播放···