7相册框架 ios

2017-03-27 12:45:23 HDFQQ188816190 阅读数 3384

一.引言

  最近项目中要用到相册和选取截图,但是系统的不太符合设计要求,所以就自己实现了一个.
  首先说说做前普及的知识.在iOS中,关于相册的系统 API有两个,准确来说其实是一个.分别为AssetsLibrary和Photos,AssetsLibrary框架是iOS7之前的关于相册的系API,而在iOS8的时候由于AssetsLibrary框架的一些问题.所以苹果推出了新的API:Photos,知道iOS9的时候直接已经废弃了AssetsLibrary框架,但是我们在项目还要兼容iOS7,所以要对这两个分别来配置下.

二. AssetsLibrary框架

  AssetsLibrary框架是 iOS7之前的相册系统框架,iOS9已被废弃,相似于一个数据集合,而想要获得某类型的照片或者照片组就得按照相应的规则去遍历数据集合,来获得想要的.这样的性能是不是太好.接下来看看主要组成该框架的类.

  • AssetsLibrary:包括相册资源集合,以及筛选,存入,权限等api类.此类大概分三大块,一块是保存图片到数据集合,一块是读取数据集合中的图片,最后一块是用户相关权限:

  保存类api 是以write开头的一些 api

- (void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef 
                         orientation:(ALAssetOrientation)orientation
                     completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock; 

保存一个图片到相册:
imageRef:保存的图片, CGImageRef类型,可由[UIImage CGImage]获得
orientation:保存图片时,图片的方向,可由[UIImage imageOrientation]或得,枚举:
    ALAssetOrientationUp 上
    ALAssetOrientationDown 下
    ALAssetOrientationLeft 左
    ALAssetOrientationRight 右
    ALAssetOrientationUpMirrored 水平上翻转
    ALAssetOrientationDownMirrored 水平下翻转
    ALAssetOrientationLeftMirrored 竖直左翻转
    ALAssetOrientationRightMirrored 竖直右翻转
completionBlock:回调 block, 定义为:typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error)
成功回调得到 iamge 的 url.这里的 url 是本地的,可以用它来检索 image,error: 回调到错误信息.
其他保存 aip 类似.

  读取类 api,这说下常用的遍历ALAssetsLibrary集合的.

- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types 
                      usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock 
                    failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;

遍历资源集合获得筛选到的相册:
types:删选条件,枚举:
   ALAssetsGroupLibrary 所有的相册(宝库本地和 iTunes 的)
   ALAssetsGroupAlbum  同步得到iTunes的和创建的(不包括共享的)
   ALAssetsGroupEvent  同步到 iTunes 的(包括相机导入的)
   ALAssetsGroupFaces  同步 iTunes 的 
   ALAssetsGroupSavedPhotos 所有相册(包括相机导入的)
   ALAssetsGroupPhotoStream itunes 同步的(包括共享的)
   ALAssetsGroupAll 所有可见的相册
enumerationBlock:成功回调 block, 定义:typedef void (^ALAssetsLibraryGroupsEnumerationResultsBlock)(ALAssetsGroup *group, BOOL *stop) 
获得 group: 每个图片或视屏组成的相册,stop: 停止遍历的标示
failureBlock:失败的回调:定义:typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error)
error: 错误信息.

  权限设置类:

+ (ALAuthorizationStatus)authorizationStatus;
类方法,返回值一个ALAuthorizationStatus的枚举类型,来表明当时的用户权限状况.
    ALAuthorizationStatusNotDetermined 用户还未做出选择 
    ALAuthorizationStatusRestricted 用户权限设置(如家长模式)
    ALAuthorizationStatusDenied 明确选择过不允许获得权限
    ALAuthorizationStatusAuthorized 同意获取
  • ALAssetsGroup:资源中某个相册资源,可以获取相册中具体照片或者视屏,添加或者获取详细相册信息,列出几个常用主要的方法:
(1):获取相册数量:
- (NSInteger)numberOfAssets;
获取数量是和你的删选条件有关filter.
(2):删选条件:
- (void)setAssetsFilter:(ALAssetsFilter *)filter;
给定一个删选条件.
filter:删选的一个条件,ALAssetsFilter类型的类方法
+ (ALAssetsFilter *)allPhotos; 所有的图片
+ (ALAssetsFilter *)allVideos; 所有的视屏
+ (ALAssetsFilter *)allAssets; 所有的资源(包括图片和视频)
(3):相册封面照片:
- (CGImageRef)posterImage;
获得相册的封面照片,该返回的图片方向是正确的.
(4):返回相册的一些相关属性:
- (id)valueForProperty:(NSString *)property;
property:相册相关的一些属性,定义了常量:
extern NSString *const ALAssetsGroupPropertyName 相册的名称 
extern NSString *const ALAssetsGroupPropertyType 相册的类型
extern NSString *const ALAssetsGroupPropertyPersistentID 相册的标示 id
extern NSString *const ALAssetsGroupPropertyURL 相册的 url
(5):获取相册中的资源:
- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock;
enumerationBlock:遍历资源回调的 block, 定义为:typedef void (^ALAssetsGroupEnumerationResultsBlock)(ALAsset *result, NSUInteger index, BOOL *stop);
result:一个图片或者资源的类,包含了资源的一些信息.
index:遍历相册时result 对应的下标位置.
stop:对遍历的操作控制.如果有特殊条件可以在某个位置停止获取资源.
  • ALAsset:相册中每个对象,包含了相片或者视频的一些信息.可以写入或者获取到一些资源.
(1):返回一个ALAssetRepresentation对象,一个ALAsset对像默认有一个ALAssetRepresentation对象,用他来获取更加详细的每个具体资源的信息,如:原图,大小,字节等.
- (ALAssetRepresentation *)defaultRepresentation;
(2):获得缩略图:
- (CGImageRef)thumbnail;
该方法获得相对质量不高的缩略图,他是对原图进行从最中间进行正方形的裁剪.改图不是按比例缩小.图片的方向的正确的.
(3):获得缩略图:
- (CGImageRef)aspectRatioThumbnail;
和上面的方法得到的都是缩略图,但是不同的是他是按图片原先的比例按比例缩小的
(4).保存图片到相册:
- (void)writeModifiedImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock;
保存图片到相册:
imageData:要保存的图片流,
metadata:保存图片的元数据,一般为 nil,如果和前面的 imageData 冲突的话,会覆盖掉前面的信息,
completionBlock:保存完的 block 回调,定义为:typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error),
assetUrl: 保存成功后映射该相片的 url
error: 保存失败信息.
  • ALAssetRepresentation:对 ALAsset 的封装,包含了每个相册的一些详细信息,可以用defaultRepresentation获得默认的那个该对象.这里要注意的是,如果是相册的话,默认是一个该对象,而如果是相机拍照的情况下.默认是有两个该对象的,一个包含了RAW 的信息,一个包含了JPEG的信息.

三.Photos 框架

  Photos 是 iOS8时苹果新推出的一个关于系统相册的新框架.改框架应该跟AssetsLibrary的处理不一样,他不是去根据条件遍历数据资源,而是根据条件直接获得指定的资源.所以相比而言,更加高效和完整.下来看看主要组成该框架的类.

  • PHAssetCollection:PHCollection的子类,相册分类,一系列的相册.如:最近删除.精选等.常用 api:
(1).该相册的类型
assetCollectionType;
PHAssetCollectionType的枚举值:
    PHAssetCollectionTypeAlbum      相册  //这是里对应的 PHAssetCollectionSubtype 用户自定义的相册文件也在其subtype  
    PHAssetCollectionTypeSmartAlbum 智能相册   //对应的为系统里的相册文件 下面的200- 211等都为其subtype  

    PHAssetCollectionTypeMoment 时刻
(2).该相册的子类型
assetCollectionSubtype;
PHAssetCollectionSubtype枚举类型:
常规的子类型
    PHAssetCollectionSubtypeAlbumRegular    常规的     
    PHAssetCollectionSubtypeAlbumSyncedEvent    使用 iTunes 同步操作过来的相册 
    PHAssetCollectionSubtypeAlbumSyncedFaces    使用 iTuens同步操作过来的人物相册
    PHAssetCollectionSubtypeAlbumSyncedAlbum     使用iTunes 同步的所有相册
    PHAssetCollectionSubtypeAlbumImported        从外界导入的相册

经分享的子类型
    PHAssetCollectionSubtypeAlbumMyPhotoStream   从相册分享得到
    PHAssetCollectionSubtypeAlbumCloudShared     从 cloud 分享得到
智能相册子类型
    PHAssetCollectionSubtypeSmartAlbumGeneric    通用的
    PHAssetCollectionSubtypeSmartAlbumPanoramas  全景
    PHAssetCollectionSubtypeSmartAlbumVideos     视屏
    PHAssetCollectionSubtypeSmartAlbumFavorites  收藏
    PHAssetCollectionSubtypeSmartAlbumTimelapses 延时视屏,也会在PHAssetCollectionSubtypeSmartAlbumVideos在出现
    PHAssetCollectionSubtypeSmartAlbumAllHidden  隐藏的
    PHAssetCollectionSubtypeSmartAlbumRecentlyAdded 最近添加
    PHAssetCollectionSubtypeSmartAlbumBursts    连拍 
    PHAssetCollectionSubtypeSmartAlbumSlomoVideos Slomo是slow motion的缩写,高速摄影慢动作解析
    PHAssetCollectionSubtypeSmartAlbumUserLibrary 用户所有的资源
    PHAssetCollectionSubtypeSmartAlbumSelfPortraits 所有前置摄像头拍的照片和视屏
    PHAssetCollectionSubtypeSmartAlbumScreenshots 所有的截屏图
不关心子类型时的全部资源
    PHAssetCollectionSubtypeAny = NSIntegerMax
(3).获得相册的集合资源
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
返回一个相册集合资源,集合里面是PHAssetCollection类型的对象.
type:相册类型, PHAssetCollectionType类型的枚举,
subtype:子类型, PHAssetCollectionSubtype的枚举,
PHFetchOptions: PHFetchOptions的一个实例,可以为空,只要是为了对获得资源做一些配置和排序等.可以为 nil.
  • PHFetchOptions:对使用 PHAsset, PHCollection, PHAssetCollection, 和 PHCollectionLis 的方法时出入的参数,主要对获取到资源做一些配置和排序等,一般为 nil, 默认使用系统的.
(1).排序
sortDescriptors;
(2).是否显示隐藏的相册,默认不显示
includeHiddenAssets
(3).获取到相册的类型
includeAssetSourceTypes;
PHAssetSourceType类型的枚举,默认PHAssetSourceTypeNone
    PHAssetSourceTypeNone   都没有,就获得到就是常规的         
    PHAssetSourceTypeUserLibrary     用户所有的
    PHAssetSourceTypeCloudShared     分享的    
    PHAssetSourceTypeiTunesSynced    iTunes 同步的
  • PHFetchResult:相册资源,包括相册中图片的数量和获取.看些常用 api.
(1).相册中图片的数量
count
(2).遍历得到相册资源中每个相册组的信息.
- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
block为成功回调,定义为:void (^)(ObjectType obj, NSUInteger idx, BOOL *stop);
obj:改参数由遍历他的集合决定,如果是PHAssetCollection 的 api 获得.那么他就是PHAssetCollection的对象,包括每组相册的一些信息;
如果是PHAsset api 获得,那他就是 PHAsset 对象,包含具体的每张图片的信息.
idx:遍历时每组相册对应在集合中的下标.
stop:用于在某时刻停止遍历资源.
  • PHAsset:包含具体的每个照片的资源信息. 看看些常用的 api.
(1).资源的原信息.
mediaType:PHAssetMediaType类型的枚举值:
    PHAssetMediaTypeUnknown 不知类型
    PHAssetMediaTypeImage   图片
    PHAssetMediaTypeVideo   视屏
    PHAssetMediaTypeAudio   音频
(2).资源的子类型.
mediaSubtypes:PHAssetMediaSubtype类型的枚举值:
    PHAssetMediaSubtypeNone               没有任何子类型
    相片子类型
    PHAssetMediaSubtypePhotoPanorama      全景图
    PHAssetMediaSubtypePhotoHDR           滤镜图
    PHAssetMediaSubtypePhotoScreenshot 截屏图
    PHAssetMediaSubtypePhotoLive 1.5s 的 photoLive   
    视屏子类型
    PHAssetMediaSubtypeVideoStreamed      流体
    PHAssetMediaSubtypeVideoHighFrameRate 高帧视屏
    PHAssetMediaSubtypeVideoTimelapse   延时拍摄视频
(3).获得 PHAsset 的集合.
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
  • PHImageManager:管理 PHAsset 的一个类,相当于对一个具体资源更好地管理和筛选.看写常用的 api.
(1).获得该实例.
+ (PHImageManager *)defaultManager;
(2).经删选和限制条件获得具体的资源UIImage.
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset 
                              targetSize:(CGSize)targetSize 
                             contentMode:(PHImageContentMode)contentMode
                                 options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
返回值: PHImageRequestID,是个常量,定义为:static const PHImageRequestID PHInvalidImageRequestID = 0;
asset:想要获得信息的PHAsset的对象,
targetSize:获得图片的尺寸大小,这里的大小是pixel,所以换算乘以[UIScreen mainScreen].scale.获得自己想要的尺寸.
如果想要原图的尺寸,直接传入PHImageManagerMaximumSize.很大很大的尺寸,系统会默认返回原图的尺寸,要注意的是传入PHImageManagerMaximumSize时,则 contentMode 无论传入什么值都会被视为PHImageContentModeDefault.
contentMode:想要图片的裁剪方式, PHImageContentMode的枚举:
    PHImageContentModeAspectFit  适合的
    PHImageContentModeAspectFill 铺满的
    PHImageContentModeDefault = PHImageContentModeAspectFit
options: PHImageRequestOptions的实例,包括控制图片版本,质量,裁剪参数等的一个类.
resultHandler:成功回调block,
result:获取到的具体图片,
info:关于图片的一些信息,如是否来自 cloud, 是否是原图等.
  • PHCachingImageManager: PHImageManager的子类,读获取图片的过程做缓存和清理的一个类.看看一些常用的 api.
(1).缓存操作.
- (void)startCachingImagesForAssets:(NSArray<PHAsset *> *)assets
                         targetSize:(CGSize)targetSize 
                        contentMode:(PHImageContentMode)contentMode
                            options:(nullable PHImageRequestOptions *)options;
assets:要缓存获取 PHAsset 类型对象的集合.
targetSize:获取时的尺寸.
contentMode:裁剪方法,
options:传入的控制参数类.
(2).取消缓存操作.
- (void)stopCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
assets:要缓存获取 PHAsset 类型对象的集合.
targetSize:获取时的尺寸.
contentMode:裁剪方法,
options:传入的控制参数类.
  • PHImageRequestOptions:控制加载图片参数的一个类.看些常用的 api.
(1).控制图片质量和获取速度的 api
deliveryMode.
PHImageRequestOptionsDeliveryMode类型的枚举,只有synchronous属性设置为 YES,即异步获取有限
    PHImageRequestOptionsDeliveryModeOpportunistic  图片质量和获取速度均衡
    PHImageRequestOptionsDeliveryModeHighQualityFormat 获取高质量图片,不保证获取速度
    PHImageRequestOptionsDeliveryModeFastFormat 快速获得,不保证质量
(2).裁剪的方式
resizeMode.
PHImageRequestOptionsResizeMode类型的枚举:
PHImageRequestOptionsResizeModeNone 不设置  
  
PHImageRequestOptionsResizeModeFast 

 
返回的图像可能和目标大小不一样并且质量较低,但效率高. 

PHImageRequestOptionsResizeModeExact


 
返回图像必须和目标大小相匹配,并且图像质量也为高质量图像

  • PHPhotoLibrary:相册权限管理,监听相册增删等.看看常用 api
(1).获得实例
+ (PHPhotoLibrary *)sharedPhotoLibrary;
(2).获得当前用户授权情况
+ (PHAuthorizationStatus)authorizationStatus;
返回一个PHAuthorizationStatus类型的枚举:
    PHAuthorizationStatusNotDetermined 用户还未选择           
    PHAuthorizationStatusRestricted 家长模式                                       不允许
    PHAuthorizationStatusDenied 不同意访问          
    PHAuthorizationStatusAuthorized 同意访问
(3).在第一次获取时,系统提示选择完后的回调方法
+ (void)requestAuthorization:(void(^)(PHAuthorizationStatus status))handler;
handler:该 block 就是用户授权选择完后的回调,可获得用户选择的结果

四.项目

  主要分为两个大模块.一起是获取相册中的图片,另一个为对获取的图片进行想要的尺寸裁剪.

  • 获取相册中的图片,单独创建一个类 WFPhotoAlbum,作为获取系统相册资源的 manager,在内部定义三个数组,在外部声明一个公开的方法用来外部调用:
//相片原图
@property (nonatomic, strong) NSMutableArray *fullPhotos;
//缩略图
@property (nonatomic, strong) NSMutableArray *thumbnails;
//相册列表资源
@property (nonatomic, strong) NSMutableArray *albums

/**
 *  获取图片方法
 *
 *  @param success 成功回调(分组列表,原图,缩略图)
 *  @param failure 失败回调(error)
 */
- (void)getPhotosSuccess:(void (^)(NSMutableArray *groupPhotos,
                                   NSMutableArray *fullPhotos,
                                   NSMutableArray *thumbnails))success 
                 failure:(void (^)(NSError *error))failure;

  在方法的实现内部,将耗时的操作放在子线程,比如读取相册资源等,吧主要的额回调提示等放在主线程.我们分 iOS8以下和 iOS8以上,在 iOS8以下,用的AssetsLibrary框架,将相片组的名称和数量记录下来:

[_albums addObject:[NSString stringWithFormat:@"%@(%li)张",[group valueForProperty:ALAssetsGroupPropertyName],group.numberOfAssets]];

  AssetsLibrary框架可以直接提供缩略图,直接保存下缩略图:

[_thumbnails addObject:[UIImage imageWithCGImage:CGImage]];

  保存原图,这个地方保存的时候要压缩下,不然相片一多就会内存警告,会 crash:

[_fullPhotos addObject:UIImageJPEGRepresentation([UIImage imageWithCGImage:imageReference], 0.4)];

  下面是iOS8以上的,用 Photos 框架的将相册的名称记录下来:

[_albums addObject:obj.localizedTitle];

  应为 Photos 采取的机制不一样,他是由条件直接获取到对应的资源,所以只要遍历得到资源,就会占用内存.在图片稍微一多就会 crash, 所以我们在这采用保存 PHAsset 对象的办法来解决这个crash, 这也是苹果官方所采用的解决办法.

PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:nil];

[assetsFetchResults enumerateObjectsUsingBlock:^(PHAsset  *_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                   [_thumbnails addObject:obj];
                   [_fullPhotos addObject:obj];
    }];

  这里要注意两点:

  • AssetsLibrary对象应该是个全局变量,而不是局部变量,如果在他的生命周期外,我们所得到的资源是空的,这和他遍历获取资源库的机制有关.
  • 在AssetsLibrary框架下,如果用户第一次选择了权限,他会根据用户的权限,会自动再去判断获取请求资源或者不请求,这个再次请求和用户点击授权的操作是系统自动处理的.保证用户受了权限,在还能去请求一次资源,正常获得资源,而 Photos 框架下是要开发者自己去管理,所以就有个那个用户授权完的一个回调,开发者需自行判断再去是否请求一次资源.

  我们在得到回调的资源时,去对遍历操作做一次缓存.

if ([self.thumbnails.firstObject isKindOfClass:[PHAsset class]]) {
            // 在资源的集合
            _imageManager = [[PHCachingImageManager alloc] init];
            //缓存操作
            [_imageManager startCachingImagesForAssets:self.thumbnails
                                            targetSize:PHImageManagerMaximumSize
                                           contentMode:PHImageContentModeAspectFill
                                               options:nil];
        }

  然后再在赋值的时候拿出记录的 PHAsset 对象,给相应的控件赋值.此处获取到的缩略图只要按照原图比例得到的小图,要是直接使用,会产生变形,所以我们还要讲此图进行裁剪,按宽高,或得图片正中间的一个正方向的图.

//赋值
if ([obj isKindOfClass:[PHAsset class]]){

        PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
        //异步
        requestOptions.synchronous = YES;
        //速度和质量均衡//synchronous ture 时有效
        requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
        //尽快提供要求左右的尺寸图
        requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
        // 遍历资源的集合,获取其中的图片
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [_imageManager requestImageForAsset:obj
                                    targetSize:CGSizeMake(125 * [UIScreen mainScreen].scale,
                                                          125 * [UIScreen mainScreen].scale)
                                   contentMode:PHImageContentModeDefault
                                       options:requestOptions
                                 resultHandler:^(UIImage *result, NSDictionary *info) {
                                     dispatch_async(dispatch_get_main_queue(), ^{
                                         cell.imageView.image = [self wf_thumbnailsCutfullPhoto:result];
                                     });

                                 }];
        });
    }

//裁剪图片,此处裁剪为125*125大的图,即为我们的缩略图
- (UIImage *)wf_thumbnailsCutfullPhoto:(UIImage*)fullPhoto
{
    CGSize newSize;
    CGImageRef imageRef = nil;
    if ((fullPhoto.size.width / fullPhoto.size.height) < 1) {
        newSize.width = fullPhoto.size.width;
        newSize.height = fullPhoto.size.width * 1;
        imageRef = CGImageCreateWithImageInRect([fullPhoto CGImage], CGRectMake(0, fabs(fullPhoto.size.height - newSize.height) / 2, newSize.width, newSize.height));

    } else {
        newSize.height = fullPhoto.size.height;
        newSize.width = fullPhoto.size.height * 1;
        imageRef = CGImageCreateWithImageInRect([fullPhoto CGImage], CGRectMake(fabs(fullPhoto.size.width - newSize.width) / 2, 0, newSize.width, newSize.height));

    }
    return [UIImage imageWithCGImage:imageRef];
}
  • 裁剪获得想要的相片尺寸.我是用 CAShapeLayer 做了个裁剪框,这样的话,就不会影响到下面图片的缩放,平移的交互,且将 layer 一定要加载在最上层.而此处的要被操作的图片则是图片原图,且该处承载图片的容器 UIImageView 也要用图片的比例去动态设置.
      平移缩放给视图加的手势来完成的,并且限制了一些操作,保证平移时图片一直在裁剪框中,缩放时图片不能小于裁剪框.这里就不写代码了,有点多,稍后附上 demo 地址,有兴趣的小伙伴可以下载看看.效果图:

    裁剪框效果图.png


      最后就是选择后确认裁剪,其实很简单,就是将在整个大 view 的图上的裁剪框内的区域生成图片,则这张图就是咋们需要用到的图片.然后经过回调到咋们需要的地方去.
- (UIImage *)private_captureImageFromView:(UIView *)view{
    CGRect screenRect = CGRectMake((self.view.frame.size.width - rectWidth) / 2,
                                   (self.view.frame.size.height - rectHeight) / 2,
                                   rectWidth,
                                   rectHeight);
    UIGraphicsBeginImageContextWithOptions(screenRect.size, NO, [UIScreen mainScreen].scale);

    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context == NULL){
        return nil;
    }
    //copy 一份图形上下位文,用来操作
    CGContextSaveGState(context);
    //将当前图行位文进行矩阵变换
    CGContextTranslateCTM(context, -screenRect.origin.x, -screenRect.origin.y);

    if( [view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]){
        //捕捉当前快照
        [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    }else{
        //layer 层渲染
        [view.layer renderInContext:context];
    }
    //图形位文退出栈
    CGContextRestoreGState(context);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

  最后这里附上demo 的地址,有兴趣的小伙伴可以下载看看.! Demo.

修注:

  在后面该项目中此模块时,发现存在内存问题.当图片很多,上千上万张,或者不停地一直滑动,就会出现 ReceiveMemoryWarning,直至程序 crash.

  出现这个问题后,在网上调研了一些资料,终于定位到问题的所在.所出现的内存问题有两个:

  • 获取完图片后管理在图片数量庞大时,稍显乏力.

  解决方法:重新做了图片获取后的管理.引入了两个类. WFAlumbModel 和 WFCacheModel ,用于管理和交接在图片和控制器之间的工作.获取的方法改动为:

/**
 获取相册的方法

 @param success 成功获取到模型 model
 */
- (void)getCameraSuccess:(void (^)(WFCacheModel *))success;

  此方法内部开辟子线程,先调用 WFCacheModel 的实例方法,将 PHFetchResult/ALAssetsLibrary 对象保存在 model 的 result 属性中.

- (WFCacheModel *)modelWithResult:(id)result{
    WFCacheModel *model = [[WFCacheModel alloc] init];
    model.result = result;
    return model;
}

  然后重写 result 的 set 方法,在 set 方法中调用 WFPhotoAlbum 的方法.

- (void)getAssetsFromFetchResult:(id)result
                         completion:(void (^)(NSArray<WFAlumbModel *> *))completion;

  在此方法中方法,创建 WFAlumbModel 对象,将 PHAsset/ALAsset 对象保存在 WFAlumbModel 的 asset 属性中.然后添加在数组中以 block 的方式返回赋值给 WFCacheModel 的 models 属性,然后在获取相册的方法中. success 成功回调到 属性 models 数组装有 asset 的 WFAlumbModel 的 WFCacheModel 的集合.这样的对所有的相关对象层级管理.分开管理.

  在控制器回调到数据源.然后给 cell 赋值的时候.在 cell 中调用方法,返回 UIImage, 赋值就可以了.

- (PHImageRequestID)getPhotosWithAsset:(id)asset
                         originalImage:(BOOL)originalImage
                            completion:(void (^)(UIImage *))completion
  • 在获取到图片的时候,我们要对图片一些裁剪.在裁剪的方法中,只创建了 CGImageRef 对象,用完了后而没有释放,所以造成内存泄露.从而引起 ReceiveMemoryWarning.

  解决办法:用完后释放该对象.

CGImageRelease(imageRef);

  做完这些优化.现在内存问题已经不复存在了,性能也挺不错的.前面对相册管理的优化由于代码篇幅,没有详细的说明.有兴趣的小伙伴可以下载 demo 看看,地址还是前面的那个,没有变哦!

-- NORMAL --

-- NORMAL --
2017-11-23 15:20:07 liuq0725 阅读数 1511

MMPhotoPicker下载链接)基于Photos框架,集成了图片选择器(支持多选、单选、对图片进行裁剪、选择原图、可预览)以及创建自定义相册,并保存图片到自定义相册。若想使用基于AssetsLibrary框架的图片选择器,点击这里

使用

  1. pod "MMPhotoPicker" ;
  2. pod install / pod update;
  3. #import <MMPhotoPicker/MMPhotoPickerController.h>.
MMPhotoPickerController属性介绍:

// 主色调[默认蓝色]
@property (nonatomic, strong) UIColor *mainColor;
// 是否回传原图 [可用于控制图片压系数]
@property (nonatomic, assign) BOOL isOrigin;
// 是否显示原图选项 [默认NO]
@property (nonatomic, assign) BOOL showOriginImageOption;
// 是否显示空相册 [默认NO]
@property (nonatomic, assign) BOOL showEmptyAlbum;
// 是否只选取一张 [默认NO]
@property (nonatomic, assign) BOOL singleImageOption;
// 是否选取一张且需要裁剪 [默认NO]
@property (nonatomic, assign) BOOL cropImageOption;
// 裁剪的大小[默认方形、屏幕宽度]
@property (nonatomic, assign) CGSize imageCropSize;
// 最大选择数目[默认9张]
@property (nonatomic, assign) NSInteger maximumNumberOfImage;
// 代理
@property (nonatomic, assign) id<MMPhotoPickerDelegate> delegate;

图片选择

MMPhotoPickerController *mmVC = [[MMPhotoPickerController alloc] init];
// 设置代理
mmVC.delegate = self;
// 设置显示空相册
mmVC.showEmptyAlbum = YES;
// 设置最大选择数目
mmVC.maximumNumberOfImage = 9;
UINavigationController *mmNav = [[UINavigationController alloc] initWithRootViewController:mmVC];
[self.navigationController presentViewController:mmNav animated:YES completion:nil];
#pragma mark - MMPhotoPickerDelegate
- (void)mmPhotoPickerController:(MMPhotoPickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info
{
     NSLog(@"%@",info);
}

- (void)mmPhotoPickerControllerDidCancel:(MMPhotoPickerController *)picker
{
     NSLog(@"Cancel");
}

保存视频/图片到自定义相册

MMPhotoUtil类中:

// 图片
+ (void)writeImageToPhotoAlbum:(UIImage *)image;
// 视频
+ (void)writeVideoToPhotoAlbum:(NSURL *)videoURL;

注意

  1. 需要在Info.plist中添加隐私授权:Privacy - Photo Library Usage Description
  2. 如果相册名称需要根据手机语言环境显示相应语言,需要在Info.plist中设置Localized resources can be mixedYES
2015-09-24 15:05:06 GGGHub 阅读数 3006

概述


在iOS中如果想要获取手机相册里面的图片或者视频的话就要用到系统自带的AssetsLibrary框架,AssetsLibrary.framework中包含以下文件

#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetsFilter.h>
#import <AssetsLibrary/ALAssetsGroup.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
  • ALAsset.h
    从本地相册获取的图片或者视频的对象都是ALAsset类型的对象。

  • ALAssetsFilter
    顾名思义是对本地所有资源的过滤筛选,可以选择只获取照片,只获取视频,或者获取所有的资源。

  • ALAssetsGroup
    本地相册分组对象,本地有多少个相薄就会有几个ALAssetsGroup类型的对象。

  • ALAssetsLibrary
    本地资源库对象

  • ALAssetRepresentation
    ALAssetRepresentation对象来获取视频或者图片的url,文件名,二进制数据,封面缩略图等


AssetsLibrary使用


使用前导入AssetsLibrary.framework

#import <AssetsLibrary/AssetsLibrary.h>

获取本地所有相薄

初始化ALAssetsLibrary对象并使用下面的方法来获取本地所有相薄

- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock

types 相薄分组类型
ALAssetsGroupType苹果官方文档定义如下

   enum {
    ALAssetsGroupLibrary     NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = (1 << 0),
    // The Library group that includes all assets.
    ALAssetsGroupAlbum       NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = (1 << 1),
    // All the albums synced from iTunes or created on the device.
    ALAssetsGroupEvent       NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = (1 << 2),
    // All the events synced from iTunes.
    ALAssetsGroupFaces       NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = (1 << 3),
    // All the faces albums synced from iTunes.
    ALAssetsGroupSavedPhotos NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = (1 << 4),
    // The Saved Photos album.
#if __IPHONE_5_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
    ALAssetsGroupPhotoStream NS_ENUM_DEPRECATED_IOS(5_0, 9_0) = (1 << 5),
    // The PhotoStream album.
#endif
    ALAssetsGroupAll         NS_ENUM_DEPRECATED_IOS(4_0, 9_0) = 0xFFFFFFFF,
    // The same as ORing together all the available group types,
};

enumerationBlock 对本地相薄进行遍历并返回遍历结果
ALAssetsLibraryGroupsEnumerationResultsBlock定义如下

ALAssetsLibraryGroupsEnumerationResultsBlock resultBlock = ^(ALAssetsGroup *group,BOOL *stop) {};

failureBlock 遍历失败
ALAssetsLibraryAccessFailureBlock定义如下

ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {};

代码示例

typedef void (^albumGroupsBlock)(NSMutableArray *groups);
-(void)setupAlbumGroups:(albumGroupsBlock)albumGroups   //获取所有相薄成功后回调
{
    NSMutableArray *groups = @[].mutableCopy;  //所有相薄数组
    ALAssetsFilter* assstsFilter = [ALAssetsFilter allAssets]; //筛选器获取所有资源
    ALAssetsLibraryGroupsEnumerationResultsBlock resultBlock = ^(ALAssetsGroup *group, BOOL *stop){           //每次遍历成功后回调
        if (group) {
            [group setAssetsFilter:assstsFilter];
            NSInteger groupType = [[group valueForProperty:ALAssetsGroupPropertyType] integerValue];
            if (groupType == ALAssetsGroupSavedPhotos) {
                [groups insertObject:group atIndex:0];  //默认相机胶卷放入数组第一位

            }
            else   
            {
                if (group.numberOfAssets>0) {
                    [groups addObject:group];
                }
            }

        }
        else  //当返回的group为nil表示已经遍历结束
        {
            if (albumGroups) {
                albumGroups([groups copy]);
            }

        }
    };
    ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
        if (albumGroups) {   //遍历失败回调
            albumGroups([groups copy]);
        }
    };
    ;
    [[[ALAssetsLibrary alloc] init] enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:resultBlock failureBlock:failureBlock];   //开始遍历
}

获取本地每个相薄中的资源

初始化ALAssetsGroup对象并使用下面的方法来获取相薄中的资源

- (void)enumerateAssetsWithOptions:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock 

options遍历相薄中资源的顺序
NSEnumerationOptions定义如下

typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
    NSEnumerationConcurrent = (1UL << 0),  // 并发读取相册中的资源,顺序不确定
    NSEnumerationReverse = (1UL << 1),    //倒序读取相册中的资源
};

enumerationBlock对相薄里资源进行遍历并返回遍历结果

ALAssetsGroupEnumerationResultsBlock resultBlock = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {};

代码示例

typedef void (^albumAssetsBlock)(NSMutableArray *assets);
-(void)setupAlbumAssets:(ALAssetsGroup *)group withAssets:(albumAssetsBlock)albumAssets
{
    NSMutableArray *assets = @[].mutableCopy;  //相薄中所有资源数组
    ALAssetsFilter* assstsFilter = [ALAssetsFilter allAssets]; //筛选器获取所有资源
    [group setAssetsFilter:assstsFilter];   //相册内资源总数
    NSInteger assetCount = [group numberOfAssets];   //相薄中资源总数
    ALAssetsGroupEnumerationResultsBlock resultBlock = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {  //资源为ALAsset类型对象
        if (asset) {
            [assets addObject:asset];
            NSString *assetType = [asset valueForProperty:ALAssetPropertyType];
            if ([assetType isEqualToString:ALAssetTypePhoto]) {
                //资源类型为图片
            }
            else if ([assetType isEqualToString:ALAssetTypeVideo]) {
                //资源类型为视频
            }
        }
        else if (assets.count >= assetCount)
        {
            if (albumAssets) {
                albumAssets([assets copy]);
            }

        };
    };
    [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:resultBlock];
    //开始遍历相薄中资源
}

实现效果


示例代码
https://github.com/GGGHub/AlbumPicker

2017-05-17 23:12:16 lee727n 阅读数 434

获取系统相册图片,首先遵守代理协议

@interface ViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate>

@end

添加点击事件

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    UIImagePickerController *vc = [UIImagePickerController new];
    //有三种  照相机  文件夹   相册
    //    vc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    vc.delegate = self;
    vc.allowsEditing = YES;
    [self presentViewController:vc animated:YES completion:nil];


}

选择图片后 要做的事 实现在代理方法中

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{

    NSLog(@"%@",info);
    UIImage *image= info[@"UIImagePickerControllerEditedImage"];


    UIImageView *iv = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
    iv.image = image;
    [self.view addSubview:iv];

    [self dismissViewControllerAnimated:YES completion:nil];
}

接下来一起去看下效果吧!

看完单选图片,一起看一下更常用的多选系统相册图片吧:
http://blog.csdn.net/lee727n/article/details/72455769

2018-07-03 09:37:13 PeaksLee 阅读数 131

一. 概要

在 iOS 设备中,照片和视频是相当重要的一部分。最近刚好在制作一个自定义的 iOS 图片选择器,顺便整理一下 iOS 中对照片框架的使用方法。在 iOS 8 出现之前,开发者只能使用 AssetsLibrary 框架来访问设备的照片库,这是一个有点跟不上 iOS 应用发展步伐以及代码设计原则但确实强大的框架,考虑到 iOS7 仍占有不少的渗透率,因此 AssetsLibrary 也是本文重点介绍的部分。而在 iOS8 出现之后,苹果提供了一个名为 PhotoKit 的框架,一个可以让应用更好地与设备照片库对接的框架,文末也会介绍一下这个框架。

另外值得强调的是,在 iOS 中,照片库并不只是照片的集合,同时也包含了视频。在 AssetsLibrary 中两者都有相同类型的对象去描述,只是类型不同而已。文中为了方便,大部分时候会使用「资源」代表 iOS 中的「照片和视频」。

二. AssetsLibrary 组成介绍

AssetsLibrary 的组成比较符合照片库本身的组成,照片库中的完整照片库对象、相册、相片都能在 AssetsLibrary 中找到一一对应的组成,这使到 AssetsLibrary 的使用变得直观而方便。

  • AssetsLibrary: 代表整个设备中的资源库(照片库),通过 AssetsLibrary 可以获取和包括设备中的照片和视频
  • ALAssetsGroup: 映射照片库中的一个相册,通过 ALAssetsGroup 可以获取某个相册的信息,相册下的资源,同时也可以对某个相册添加资源。
  • ALAsset: 映射照片库中的一个照片或视频,通过 ALAsset 可以获取某个照片或视频的详细信息,或者保存照片和视频。
  • ALAssetRepresentation: ALAssetRepresentation 是对 ALAsset 的封装(但不是其子类),可以更方便地获取 ALAsset 中的资源信息,每个 ALAsset 都有至少有一个 ALAssetRepresentation 对象,可以通过 defaultRepresentation 获取。而例如使用系统相机应用拍摄的 RAW + JPEG 照片,则会有两个 ALAssetRepresentation,一个封装了照片的 RAW 信息,另一个则封装了照片的 JPEG 信息。

三. AssetsLibrary 的基本使用

AssetsLibrary 的功能很多,基本可以分为对资源的获取/保存两个部分,保存的部分相对简单,API 也比较少,因此这里不作详细介绍。获取资源的 API 则比较丰富了,一个常见的使用大量 AssetsLibrary API 的例子就是图片选择器(ALAsset Picker)。要制作一个图片选择器,思路应该是获取照片库-列出所有相册-展示相册中的所有图片-预览图片大图。

首先是要检查 App 是否有照片操作授权:

1
2
3
4
5
6
7
8
9
10
NSString*tipTextWhenNoPhotosAuthorization; // 提示语
// 获取当前应用对照片的访问授权状态
ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus];
// 如果没有获取访问授权,或者访问授权状态已经被明确禁止,则显示提示语,引导用户开启授权
if(authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {
    NSDictionary*mainInfoDictionary = [[NSBundlemainBundle] infoDictionary];
    NSString*appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
    tipTextWhenNoPhotosAuthorization = [NSStringstringWithFormat:@"请在设备的\"设置-隐私-照片\"选项中,允许%@访问你的手机相册", appName];
    // 展示提示语
}

如果已经获取授权,则可以获取相册列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_assetsLibrary = [[ALAssetsLibrary alloc] init];
_albumsArray = [[NSMutableArrayalloc] init];
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL*stop) {
    if(group) {
        [group setAssetsFilter:[ALAssetsFilter allPhotos]];
        if(group.numberOfAssets > 0) {
            // 把相册储存到数组中,方便后面展示相册时使用
            [_albumsArray addObject:group];
        }
    }else{
        if([_albumsArray count] > 0) {
            // 把所有的相册储存完毕,可以展示相册列表
        }else{
            // 没有任何有资源的相册,输出提示
        }
    }
} failureBlock:^(NSError*error) {
    NSLog(@"Asset group not found!\n");
}];

上面的代码中,遍历出所有的相册列表,并把相册中资源数不为空的相册 ALAssetGroup 对象的引用储存到一个数组中。这里需要强调几点:

  • iOS 中允许相册为空,即相册中没有任何资源,如果不希望获取空相册,则需要像上面的代码中那样手动过滤
  • ALAssetsGroup 有一个 setAssetsFilter 的方法,可以传入一个过滤器,控制只获取相册中的照片或只获取视频。一旦设置过滤,ALAssetsGroup 中资源列表和资源数量的获取也会被自动更新。
  • 整个 AssetsLibrary 中对相册、资源的获取和保存都是使用异步处理(Asynchronous),这是考虑到资源文件体积相当比较大(还可能很大)。例如上面的遍历相册操作,相册的结果使用 block 输出,如果相册遍历完毕,则最后一次输出的 block 中的 group 参数值为 nil。而 stop 参数则是用于手工停止遍历,只要把 *stop 置 YES,则会停止下一次的遍历。关于这一点常常会引起误会,所以需要注意。

现在,已经可以获取相册了,接下来是获取相册中的资源:

1
2
3
4
5
6
7
8
_imagesAssetArray = [[NSMutableArrayalloc] init];
[assetsGroup enumerateAssetsWithOptions:NSEnumerationReverseusingBlock:^(ALAsset *result, NSUIntegerindex, BOOL*stop) {
    if(result) {
        [_imagesAssetArray addObject:result];
    }else{
        // result 为 nil,即遍历相片或视频完毕,可以展示资源列表
    }
}];

跟遍历相册的过程类似,遍历相片也是使用一系列的异步方法,其中上面的方法所输出的 block 中,除了 result 参数表示资源信息,stop 用于手工停止遍历外,还提供了一个 index 参数,这个参数表示资源的索引。一般来说,展示资源列表都会使用缩略图(result.thumbnail),因此即使资源很多,遍历资源的速度也会相当快。但如果确实需要加载资源的高清图或者其他耗时的处理,则可以利用上面的 index 参数和 stop 参数做一个分段拉取资源。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
NSUInteger_targetIndex; // index 目标值,拉取资源直到这个值就手工停止拉取
NSUInteger_currentIndex; // 当前 index,每次拉取资源时从这个值开始
 
_targetIndex = 50;
_currentIndex = 0;
 
- (void)loadAssetWithAssetsGroup:(assetsGroup *)assetsGroup {
    [assetsGroup enumerateAssetsAtIndexes:[NSIndexSetindexSetWithIndex:_currentIndex] options:NSEnumerationReverseusingBlock:^(ALAsset *result, NSUIntegerindex, BOOL*stop) {
        _currentIndex = index;
        if(index > _targetIndex) {
            // 拉取资源的索引如果比目标值大,则停止拉取
            *stop = YES;
        }else{
            if(result) {
                [_imagesAssetArray addObject:result];
            }else{
                // result 为 nil,即遍历相片或视频完毕
            }
        }
    }];
}
 
// 之前拉取的数据已经显示完毕,需要展示新数据,重新调用 loadAssetWithAssetsGroup 方法,并根据需要更新 _targetIndex 的值

最后一步是获取图片详细信息,例如:

1
2
3
4
// 获取资源图片的详细资源信息,其中 imageAsset 是某个资源的 ALAsset 对象
ALAssetRepresentation *representation = [imageAsset defaultRepresentation];
// 获取资源图片的 fullScreenImage
UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];

对于一个 ALAssetRepresentation,里面包含了图片的多个版本。最常用的是 fullResolutionImage 和 fullScreenImage。fullResolutionImage 是图片的原图,通过 fullResolutionImage 获取的图片没有任何处理,包括通过系统相册中“编辑”功能处理后的信息也没有被包含其中,因此需要展示“编辑”功能处理后的信息,使用 fullResolutionImage 就比较不方便,另外 fullResolutionImage 的拉取也会比较慢,在多张 fullResolutionImage 中切换时能明显感觉到图片的加载过程。因此这里建议获取图片的 fullScreenImage,它是图片的全屏图版本,这个版本包含了通过系统相册中“编辑”功能处理后的信息,同时也是一张缩略图,但图片的失真很少,缺点是图片的尺寸是一个适应屏幕大小的版本,因此展示图片时需要作出额外处理,但考虑到加载速度非常快的原因(在多张图片之间切换感受不到图片加载耗时),仍建议使用 fullScreenImage。

系统相册的处理过程大概也是如上,可以看出,在整个过程中并没有使用到图片的 fullResolutionImage,从相册列表展示到最终查看资源,都是使用缩略图,这也是 iOS 相册加载快的一个重要原因。

三. AssetsLibrary 的坑点

作为一套老框架,AssetsLibrary 不但有坑,而且还不少,除了上面提到的资源异步拉取时需要注意的事项,下面几点也是值得注意的:

1. AssetsLibrary 实例需要强引用

实例一个 AssetsLibrary 后,如上面所示,我们可以通过一系列枚举方法获取到需要的相册和资源,并把其储存到数组中,方便用于展示。但是,当我们把这些获取到的相册和资源储存到数组时,实际上只是在数组中储存了这些相册和资源在 AssetsLibrary 中的引用(指针),因而无论把相册和资源储存数组后如何利用这些数据,都首先需要确保 AssetsLibrary 没有被 ARC 释放,否则把数据从数组中取出来时,会发现对应的引用数据已经丢失(参见下图)。这一点较为容易被忽略,因此建议在使用 AssetsLibrary 的 viewController 中,把 AssetsLibrary 作为一个强持有的 property 或私有变量,避免在枚举出 AssetsLibrary 中所需要的数据后,AssetsLibrary 就被 ARC 释放了。

如下图:实例化一个 AssetsLibrary 的局部变量,枚举所有相册并储存在名为 _albumsArray 的数组中,展示相册时再次查看数组,发现 ALAssetsGroup 中的数据已经丢失。

ALAssetsLibrary_release

2. AssetsLibrary 遵循写入优先原则

写入优先也就是說,在利用 AssetsLibrary 读取资源的过程中,有任何其它的进程(不一定是同一个 App)在保存资源时,就会收到 ALAssetsLibraryChangedNotification,让用户自行中断读取操作。最常见的就是读取 fullResolutionImage 时,用进程在写入,由于读取 fullResolutionImage 耗时较长,很容易就会 exception。

3. 开启 Photo Stream 容易导致 exception

本质上,这跟上面的 AssetsLibrary 遵循写入优先原则是同一个问题。如果用户开启了共享照片流(Photo Stream),共享照片流会以 mstreamd 的方式“偷偷”执行,当有人把相片写入 Camera Roll 时,它就会自动保存到 Photo Stream Album 中,如果用户刚好在读取,那就跟上面说的一样产生 exception 了。由于共享照片流是用户决定是否要开启的,所以开发者无法改变,但是可以通过下面的接口在需要保护的时刻关闭监听共享照片流产生的频繁通知信息。

1
[ALAssetsLibrary disableSharedPhotoStreamsSupport];

四. PhotoKit 简介

PhotoKit 是一套比 AssetsLibrary 更完整也更高效的库,对资源的处理跟 AssetsLibrary 也有很大的不同。

首先简单介绍几个概念:

  • PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源
  • PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值
  • PHFetchResult: 表示一系列的资源集合,也可以是相册的集合
  • PHAssetCollection: 表示一个相册或者一个时刻,或者是一个「智能相册(系统提供的特定的一系列相册,例如:最近删除,视频列表,收藏等等,如下图所示)
  • PHImageManager: 用于处理资源的加载,加载图片的过程带有缓存处理,可以通过传入一个 PHImageRequestOptions 控制资源的输出尺寸等规格
  • PHImageRequestOptions: 如上面所说,控制加载图片时的一系列参数

下图中 UITableView 的第二个 section 就是 PhotoKit 所列出的所有智能相册

photokit-album-list

再列出几个代码片段,展示如何获取相册以及某个相册下资源的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 列出所有相册智能相册
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
 
// 列出所有用户创建的相册
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
 
// 获取所有资源的集合,并按资源的创建时间排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptorsortDescriptorWithKey:@"creationDate"ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
 
// 在资源的集合中获取第一个集合,并获取其中的图片
PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
PHAsset *asset = assetsFetchResults[0];
[imageManager requestImageForAsset:asset
                         targetSize:SomeSize
                        contentMode:PHImageContentModeAspectFill
                            options:nil
                      resultHandler:^(UIImage *result, NSDictionary*info) {
                           
                          // 得到一张 UIImage,展示到界面上
                           
                      }];

结合上面几个代码片段上看,PhotoKit 相对 AssetsLibrary 主要有三点重要的改进:

  • 从 AssetsLibrary 中获取数据,无论是相册,还是资源,本质上都是使用枚举的方式,遍历照片库取得相应的数据。而 PhotoKit 则是通过传入参数,直接获取相应的数据,因而效率会提高不少。
  • 在 AssetsLibrary 中,相册和资源是对应不同的对象(ALAssetGroup 和 ALAsset),因此获取相册和获取资源是两个完全没有关联的接口。而 PhotoKit 中则有 PHFetchResult 这个可以统一储存相册或资源的对象,因此处理相册和资源时也会比较方便。
  • PhotoKit 返回资源结果时,同时返回了资源的元数据,获取元数据在 AssetsLibrary 中是很难办到的一件事。同时通过 PHAsset,开发者还能直接获取资源是否被收藏(favorite)和隐藏(hidden),拍摄图片时是否开启了 HDR 或全景模式,甚至能通过一张连拍图片获取到连拍图片中的其他图片。这也是文章开头说的,PhotoKit 能更好地与设备照片库接入的一个重要因素。

关于 PhotoKit,建议可以参考 Apple 的 Example app using Photos framework

iOS 自定义相册

阅读数 622