一、获取单张图片
1、利用UIImagePickerController可以从系统自带的App中获得图片
2、设置代理,遵守代理协议
@interface ViewController () <UIImagePickerControllerDelegate,UINavigationControllerDelegate>
3、实现代理方法didFinishPickingMediaWithInfo
二、获取多张图片
思路:
1、导入头文件#import <Photos/Photos.h>
2、PHAsset:一个资源,比如一张图片或者一段视频
3、PHAssetCollection:一个相簿
4、PHImageManager 图片管理者,是单例,发送请求才能从asset获取图片
5、PHImageRequestOptions图片请求选项
6、注意:这个类是iOS8开始推广,iOS9开始废弃之前的方法
系统适配iOS8之前,用下面的这个库里面的API
#import <AssetsLibrary/AssetsLibrary.h>
获取资源
照片库中有两种资源可供获取:PHAsset和PHCollection,前者代表图像或视频对象,后者是前者的集合或者自身类型的集合。PHCollection是一个基类,有PHAssetCollection和PHCollectionList两个子类,分别代表Photos里面的相册和文件夹。PHCollectionList里面可嵌套PHAssetCollection和自身类型,还支持多重嵌套。获取PHAsset以及PHAssetCollection的过程类似于CoreData,如下所示,只能通过类方法来返回PHFetchResult,遍历返回结果来获取需要的资源。
注意,PHAsset、PHAssetCollection和PHCollectionList都是轻量级的不可变对象,使用这些类时并没有将其代表的图像或视频或者集合载入内存中,要使用其代表的图像或视频,需要通过PHImageManager类来请求。
请求图像
关于PHImageManager类,
- requestImageForAsset:targetSize:contentMode:options:resultHandler:
你不应该生成该类的实例,而是应该使用该类的提供的单例对象,该方法提供指定的尺寸的图像,与ALAssetLibrary库相比,没有了方便的缩略图提供,但是ALAssetLibrary库提供的缩略图往往尺寸太小而且质量很低,用在TableView上还行。
该方法在默认情况下是异步执行的,而且Photos库可能会很多次执行resultHandler块,因为对于制定的尺寸,Photos可能会提供底质量的图像已供临时显示,随后会将指定尺寸的图像返回,如果指定尺寸的高质量的图像有缓存,那么直接提供高质量的图像,而这些行为,可以通过options参数来定制。
PHImageRequestOptions类用于定制请求,上面的方法返回指定尺寸的图像,如果你仅仅指定必要的参数而没有对options进行配置的话,返回的图像尺寸将会是原始图像的尺寸,或者你指定的尺寸很小,这时候会按照你的要求来返回接近改尺寸的图像。
PHImageRequestOptions有以下几个重要的属性:
synchronous:指定请求是否同步执行
resizeMode:对请求的图像怎样缩放。有三种选择:none,不缩放;Fast,尽快的提供接近或者稍微大于要求的尺寸,Exact,精准的提供要求的尺寸。
deliveryModel:图像质量,有三中值:Opportunistic,在速度与质量中均衡,HighQualityFormat,不管花费多长的时间,提供高质量的图像;FastFormat,以最快的速度提供好的质量。这个属性只有在synchronous为true时有效。
normalizedCropRect:用于对原始尺寸的图像进行裁剪,基于比例坐标,只在resizeMode为Exact时有效
resizeMode默认是None,这也造成了返回图像尺寸与要求尺寸不符,这点需要注意。这个要返回一个指定尺寸的图像要避免两层陷阱:一定要指定options参数,resizeMode不能为None。
除了必要的请求图像或是视频的功能外,PHImageManager添加了两大功能:
1、缓存图像,由于其之类PHCachingImageManager实现,缓存效率和空间管理能满足大部分场景的需求;
2、裁剪图像,这个功能很久以前就有强烈的需求。
localldentifier vs URL
Photos框架推出时,和原来的照片库AssetLibrary框架之间还有些交互,PHAsset类的+ fetchAssetWithALAssetURLs:options: 和PHAssetCollection类的+ fetchAssetCollectionsWithALAssetGroupURLs:options:可以利用原来的AssetsLibrary提供的URL进行转化,而在iOS9 中,原来的照片框架AssertLibrary已经被废弃了,官方要淡化照片库中URL的概念,改之使用一个标识符唯一代表一个资源,Photos框架中的根类PHObject只有一个公开接口localIdentifier,AssetsLibrary框架中无论是Asset还是AssetGroup的URL也是唯一标识符,而且同时还是动态变化的,每次启动应用后获取的URL和上一次是不一样的,而且AssetGroup有一个PersistentID与PHObject的localidentifier 类似,但获取比较麻烦。
localIdentifier属性带来的最大好处就是PHObject类实现了NSCopying协议,可以直接使用localIdentifier属性对PHObject以及子类对象进行对比是否同一个对象。
获取指定类型相册
通过PHAssetCollection的以下方法来获取指定的相册:
func fetchAssetCollectionsWithType(_ type: PHAssetCollectionType, subtype subtype: PHAssetCollectionSubtype, options options: PHFetchOptions?) -> PHFetchResult
这个方法需要至少指定两个参数:
enum PHAssetCollectionType : Int {
case Album //从 iTunes 同步来的相册,以及用户在 Photos 中自己建立的相册
case SmartAlbum //经由相机得来的相册
case Moment //Photos 为我们自动生成的时间分组的相册}
enum PHAssetCollectionSubtype : Int {
case AlbumRegular //用户在 Photos 中创建的相册,也就是我所谓的逻辑相册
case AlbumSyncedEvent //使用 iTunes 从 Photos 照片库或者 iPhoto 照片库同步过来的事件。然而,在iTunes 12 以及iOS 9.0 beta4上,选用该类型没法获取同步的事件相册,而必须使用AlbumSyncedAlbum。
case AlbumSyncedFaces //使用 iTunes 从 Photos 照片库或者 iPhoto 照片库同步的人物相册。
case AlbumSyncedAlbum //做了 AlbumSyncedEvent 应该做的事
case AlbumImported //从相机或是外部存储导入的相册,完全没有这方面的使用经验,没法验证。
case AlbumMyPhotoStream //用户的 iCloud 照片流
case AlbumCloudShared //用户使用 iCloud 共享的相册
case SmartAlbumGeneric //文档解释为非特殊类型的相册,主要包括从 iPhoto 同步过来的相册。由于本人的 iPhoto 已被 Photos 替代,无法验证。不过,在我的 iPad mini 上是无法获取的,而下面类型的相册,尽管没有包含照片或视频,但能够获取到。 case SmartAlbumPanoramas //相机拍摄的全景照片
case SmartAlbumVideos //相机拍摄的视频
case SmartAlbumFavorites //收藏文件夹
case SmartAlbumTimelapses //延时视频文件夹,同时也会出现在视频文件夹中
case SmartAlbumAllHidden //包含隐藏照片或视频的文件夹
case SmartAlbumRecentlyAdded //相机近期拍摄的照片或视频
case SmartAlbumBursts //连拍模式拍摄的照片,在 iPad mini 上按住快门不放就可以了,但是照片依然没有存放在这个文件夹下,而是在相机相册里。
case SmartAlbumSlomoVideos //Slomo 是 slow motion 的缩写,高速摄影慢动作解析,在该模式下,iOS 设备以120帧拍摄。不过我的 iPad mini 不支持,没法验证。 case SmartAlbumUserLibrary //这个命名最神奇了,就是相机相册,所有相机拍摄的照片或视频都会出现在该相册中,而且使用其他应用保存的照片也会出现在这里。
case Any //包含所有类型}
注意,获取指定类型的相册时,主类型和子类型要匹配,如果不匹配,系统会按照Any子类型来处理,对于Moment类型,子类型使用Any.
1.获取用户自己建立的相册和文件夹我称之为逻辑相册,非系统相册和从 iTunes 同步来的相册)有两种方法:
PHCollection.fetchTopLevelUserCollectionsWithOptions(nil)
PHAssetCollection.fetchAssetCollectionsWithType(.Album,subtype:.AlbumRegular,option:nil)
在没有提供PHOptions的情况下,返回的PHFetchResult结果是按相册的建立时间排序的,最新的在前面
2.获取相机相册:
PHAssetCollection.fetchAssetCollectionWithType(.SmartAlbum,subtype:.SmartAlbumUserLibrary,options:nil)
另外:PHAsset
的获取方式在 iOS 8.1 后发生了一些变化。以下的两个方法在 iOS 8.1后不再包含从 iTunes 同步以及在 iCloud 中的照片和视频。要获取 iOS 设备上本地的所有照片和资源只能从 PHAssetCollection 入手了。
+ fetchAssetsWithMediaType:options:
+ fetchAssetsWithOptions:
添加、删除、编辑
对照片库进行操作,可参见官方文档Requesting Changes to the Photo Library,照片库中的资源都有对应的变更请求类:PHAssetChangeRequest,PHAssetCollectionChangeRequest和PHCollectionListChangeRequest,而这些操作的请求都要求在PHPhotoLibrary的performChanges(_ changeBlock: dispatch_block_t!, completionHandler completionHandler: ((Bool, NSError!) -> Void)!)中的changeBlock中执行。注意这里只是发出请求并没有做出实质的更改,因此想要根据更改结果更新UI的话不要在completionHandler中进行,而应该在photoLibraryDidChange(changeInfo:PHChange!)中进行。三种变更请求中,删除和编辑操作都比较简单,而添加操作有需要注意的地方。
添加操作:placeholder的用处
在相册中添加照片:
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for AssetCollection:album)
albumChangeRequest.addAssets([assetPlaceholder])
在文件夹中添加相册:
let fetchResult = PHCollection.fetchCollectionsInCollectionList(collectionList,options:nil)
let createSubAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(title!)
let albumPlaceholder = createSubAlbumRequest.placeholderForCreatedAssetCollection
let folderChangeRequest = PHCollectionListChangeRequest.init(forCollectionList:collectionList,childCollections:fetchResult)
folderChangeRequest?.addChildCollections([albumPlaceholder])
在文件夹中添加子文件夹:
let fetchResult = PHCollection.fetchCollectionsInCollectionList(collectionList,options:nil)
let createSubAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForCollectionListWithTitle(title!)
let subfolderPlaceholder = createSubFolderRequest.placeholderForCreatedCollectionList
let folderChangeRequest = PHCollectionListChangeRequest.init(forCollectionList:collectionList,childCollections:fetchResult)
folderChangeRequest?.addChildCollections([subfolderPlaceholder])
处理变更
对相册发出变更请求后,系统会通知用户是否允许,用户允许后才会发生实质上的变化,系统会发布通知。
首先,注册成为PHPhotoLibrary的观察者来接收变化通知:
PHPhotoLibrary.shareLibrary().registerChangeObserver(self)
然后,实现PHPhotoLibraryChangeObserver协议的photoLibraryDidChange(changeInfo:PHChange!)。官方一个很好的例子:Handing Changes:An Example,有以下几点需要注意:
- 在photoLibraryDidChange(changeInfo:PHChange!)的实现里将所有处理放在主线程里处理;
- 所有PHPhotoLibrary的观察者都会收到通知,不管观察者本身引用的内容是否发生变化,因此要根据观察者的情况来对通知进行过滤。从参数PhChange对象里能获得所有的变化,通过changeDetailsForObject:和changeDetailsForFetchResult:来获取细节。changeDetailsForObject:获取的细节只是PHobject子类对象本身的信息变化。对一个PHFetchResult对象使用changeDetailsForFetchResult:获取的细节中只包含该PHFetchResult对象变化的信息,可以利用这点来对通知进行过滤处理。
- 通过 changeDetailsForFetchResult:获取的PHFetchResultChangeDetails对象,包含了FetchResult的结果的所有变化情况以及FetchResult的成员变化前后的数据,需要注意的是成员变化的通知。
例如,通过
var rootCollectionsFetchResult = PHCollection.fetchTopLevelUserCollectionsWithOptions(nil)
获取所有用户建立的相册和文件夹,在photoLibraryDidChange(changeInfo:PHChange!)中通过以下方法获得PHFetchResultChangeDetails对象。
let fetchChangeDetails = changeInstance.changeDetailsForFetchResult(rootCollectionsFetchResult)
fetchChangeDetails.changeObject返回一组其内容或元数据发生变化的成员,返回的成员是跟新后的成员对象。当用户对某个文件夹内的相册或子文件夹进行添加、删除和编辑操作即文件夹的内容而不是文件夹本身的属性发生变化时,通知中会该变化的信息,实际上只有在文件夹中添加相册或子文件夹时才会在fetchChangeDetails.changedObject中有所反应,而删除成员或是修改元数据等操作都不会再通知中有所反应,你需要使用其他手段来跟踪变化
获得所有相簿的原图
(void)getOriginalImage
{
//获得所有的自定义相簿
PHFetchResult<PHAssetCollection *> * assetCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionAlbumRegular options:nil];
//遍历所有的自定义的相簿
for(PHAssetCollection * assetCollection in assetCollections){
[self enumerateAssetsInAssetCollection:assetCollection original:yes];
}
//获得相机胶卷
PHAssetCollection * cameraRoll = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil].lastObject;
//遍历相机胶卷,获取大图
[self enumerateAssetsInAssetCollection:cameraRoll original:YES];
}
获得所有相簿中的缩略图
- (void)getThumbnailImages
{
//获得所有的自定义相簿
PHFetchResult <PHAssetCollection *> assetCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
//遍历所有的自定义相簿
for(PHAssetCollection * assetCollection in assetCollections){
[self enumerateAssetsInAssetCollection:assetCollection original:NO];
}
// 获得相机胶卷
PHAssetCollection *cameraRoll = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil].lastObject;
[self enumerateAssetsInAssetCollection:cameraRoll original:NO];
}
遍历相册
/**
* 遍历相簿中的所有图片
* @param assetCollection 相簿
* @param original 是否要原图
*/
- (void)enumerateAssetsInAssetCollection:(PHAssetCollection *)assetCollection original:(BOOL)original{
NSLog(@"相簿名:%@", assetCollection.localizedTitle);
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
// 同步获得图片, 只会返回1张图片
options.synchronous = YES;
// 获得某个相簿中的所有PHAsset对象
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsInAssetCollection:assetCollection options:nil];
for (PHAsset *asset in assets) {
// 是否要原图
CGSize size = original ? CGSizeMake(asset.pixelWidth, asset.pixelHeight) : CGSizeZero;
// 从asset中获得图片
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
NSLog(@"%@", result);
}];
}}