需求

很多公司项目中都会使用到相册,以及相机,保存图片,从相册中选取图片等等操作。本文将详细介绍该功能如何实现优化,以及使用一些优秀的第三方库来辅助完成我们的需求。

photos framework 的使用

Photos Framework reference

Classes

PHAdjustmentData

/*
When a user edits an asset, Photos saves a PHAdjustmentData
object along with the modified image or video data. 在用户编辑一个 asset 时,相册会存储该 asset 在修改 image 或者 video 数
据的过程中
*/
PHAdjustmentData

什么是 asset?

PHAsset

/*
A PHAsset object represents an image or video file that appears
in the Photos app, including iCloud Photos content. 一个 PHAsset 对象代表相册中或者云存储中的一个 image 或者 video 文件。
*/

PHAssetChangeRequest

/*
You create and use PHAssetChangeRequest objects within a photo
library change block to create, delete, or modify PHAsset
objects. 当你在相册中增删改 PHAsset 对象时需要使用 PHAssetChangeRequest 对象
*/

PHAssetCreationRequest

/*
You create and use PHAssetChangeRequest objects within a photo
library change block to create, delete, or modify PHAsset
objects. 当你在相册中增删改 PHAsset 对象时需要使用 PHAssetChangeRequest 对象
*/

PHAssetCollectionChangeRequest

/*
You create and use PHAssetCollectionChangeRequest objects
within a photo library change block to create, delete, or
modify PHAssetCollection objects. 当你对 photo library 即 assetCollection 进行增删改操作时,使用
PHAssetCollectionChangeRequest 对象
*/

PHAssetResourceCreationOptions

/*
You use a PHAssetResourceCreationOptions object to specify
options when creating a new asset from data resources with a
PHAssetCreationRequest object. 通过 PHAssetResourceCreationOptions 对象来指定当创建一个
新的 asset 的 options。
*/

PHAssetResourceManager

/*
The shared PHAssetResourceManager object provides methods for
accessing the underlying data storage for the resources
associated with a Photos asset. PHAssetResourceManager 对象提供方法访问关联相机 asset 资源的基础数据存

*/

PHAssetCollection

/*
A PHAssetCollection object represents a collection of photo or
video assets. 一个 PHAssetCollection 对象就代表一个相册
*/

重点:

PHPhotoLibrary

/*
The shared PHPhotoLibrary object represents the user’s Photos
library—the entire set of assets and collections managed by the
Photos app, including objects stored on the local device and
(if enabled) in iCloud Photos. 公共的 PHPhotoLibrary 对象代表用户的相册库,所有的图片和相册管理都
要 PHPhotoLibrary 管理。
*/

保存图片到自定义相册

保存图片一般分为三个步骤:

保存图片到【相机胶卷】
拥有一个【自定义相册】
添加刚才保存的图片到【自定义相册】

需要用到的框架和函数:

c语言函数:只能完成第一个步骤,比较简单
AssetsLibrary 框架:有 bug
Photos 框架 :iOS8以后可以使用

如果单纯的只需要完成第一步则推荐使用一个 c 语言函数就可以搞定。但是如果要保存图片到自定义相册,则需要使用框架。iOS7以前使用 AssetsLibrary 框架,但是该框架的稳定性不高。而 iOS 8 以后的 Photos 框架将会取代 AssetsLibrary 框架完成这一功能。根据目前 app 市场的版本占有率,推荐使用 Photos。

将图片保存到相机胶卷(c 语言函数实现)

/**
* 该 c 语言函数是将图片保存到相机胶卷中
* 第一个参数:image 图片
* 第二个参数:target
* 第三个参数:selector 方法名规定使用以下方法名 :- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
* 第四个参数:保存完毕后会将第四个参数传给函数调用者
* 方法作用:将图片保存到相机胶卷中,保存完毕后会调用 target 的 selector 方法
*/
UIImageWriteToSavedPhotosAlbum(self.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{ }

如果第三个参数方法名没有按照规范,则有可能会报如下错误:

/*
错误信息:-[NSInvocation setArgument:atIndex:]: index (2) out of bounds [-1, 1]
错误解释:参数越界错误,方法的参数个数和实际传递的参数个数不一致
*/

保存图片到相机胶卷(使用 Photos 框架)

由上面 Photos 框架的介绍可以看出,我们要对相册中的 asset 进行增删改操作时,用到 PHAssetChangeRequest 类,而在 PHAssetChangeRequest 的头文件可以看到,想要在相册胶卷中保存图片。要用到下面方法:

[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
/*
但是单独使用时,程序会报错。错误如下: 错误信息:This method can only be called from inside of -
[PHPhotoLibrary performChanges:completionHandler:] or -
[PHPhotoLibrary performChangesAndWait:error:] 错误信息说的很清楚,这个方法只能在 PHPhotoLibrary 类中的这两个方法中
使用。
在 iOS app 中,任何对 photos 的增删改操作,都一定会放在上述错误信息的
两个方法中。 */

具体代码如下:

/**
* 该方法是异步执行的,不会阻塞当前线程,而且执行完后会来到
* completionHandler 的 block 中。
*/
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
} completionHandler:^(BOOL success, NSError * _Nullable error) { }]; /**
* 该方法是同步执行的。在当前线程 如果执行失败 error 将会有值。
*/
NSError *error = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
} error:&error];

上述代码执行的操作是:将图片保存到相机胶卷中。

拥有一个自定义相册

通过对 photos 框架的了解,我们知道,创建一个自定义相册,我们需要用到
PHAssetCollectionChangeRequest 类。而进入其头文件发现,仍然必须在上述的 PHPhotoLibrary 类的两个 block 中执行操作。

代码如下:

#pragma mark - 使用 photo 框架创建自定义名称的相册 并获取自定义到自定义相册
#pragma mark - - (PHAssetCollection *)createCustomAssetCollection
{
// 获取 app 名称
NSString *title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey]; NSError *error = nil; // 查找 app 中是否有该相册 如果已经有了 就不再创建
/**
* 参数一 枚举:
* PHAssetCollectionTypeAlbum = 1, 用户自定义相册
* PHAssetCollectionTypeSmartAlbum = 2, 系统相册
* PHAssetCollectionTypeMoment = 3, 按时间排序的相册
*
* 参数二 枚举:PHAssetCollectionSubtype
* 参数二的枚举有非常多,但是可以根据识别单词来找出我们想要的。
* 比如:PHAssetCollectionTypeSmartAlbum 系统相册 PHAssetCollectionSubtypeSmartAlbumUserLibrary 用户相册 就能获取到相机胶卷
* PHAssetCollectionSubtypeAlbumRegular 常规相册
*/
PHFetchResult<PHAssetCollection *> *result = [PHAssetCollection fetchAssetCollectionsWithType:(PHAssetCollectionTypeAlbum)
subtype:(PHAssetCollectionSubtypeAlbumRegular)
options:nil]; for (PHAssetCollection *collection in result) {
if ([collection.localizedTitle isEqualToString:title]) { // 说明 app 中存在该相册
return collection;
}
} /** 来到这里说明相册不存在 需要创建相册 **/
__block NSString *createdCustomAssetCollectionIdentifier = nil;
// 创建和 app 名称一样的 相册
/**
* 注意:这个方法只是告诉 photos 我要创建一个相册,并没有真的创建
* 必须等到 performChangesAndWait block 执行完毕后才会
* 真的创建相册。
*/
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *collectionChangeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
/**
* collectionChangeRequest 即使我们告诉 photos 要创建相册,但是此时还没有
* 创建相册,因此现在我们并不能拿到所创建的相册,我们的需求是:将图片保存到
* 自定义的相册中,因此我们需要拿到自己创建的相册,从头文件可以看出,collectionChangeRequest
* 中有一个占位相册,placeholderForCreatedAssetCollection ,这个占位相册
* 虽然不是我们所创建的,但是其 identifier 和我们所创建的自定义相册的 identifier
* 是相同的。所以想要拿到我们自定义的相册,必须保存这个 identifier,等 photos app
* 创建完成后通过 identifier 来拿到我们自定义的相册
*/
createdCustomAssetCollectionIdentifier = collectionChangeRequest.placeholderForCreatedAssetCollection.localIdentifier;
} error:&error]; // 这里 block 结束了,因此相册也创建完毕了
if (error) {
NSLog(@"创建相册失败");
}
return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createdCustomAssetCollectionIdentifier] options:nil].firstObject;
}

将相机胶卷的相片存储到自定义相册中

在 photos 框架中,我们并不能直接拿到相册 PHAssetCollection 类来进行增删改操作,需要一个中间类也就是上面所讲的 PHAssetCollectionChangeRequest 类。 步骤如下:

先通过 PHAssetCollection 对象创建 PHAssetCollectionChangeRequest对象
通过 PHAssetCollectionChangeRequest 对象来进行增删改操作。

#pragma mark - 将图片保存到自定义相册中
#pragma mark - - (void)saveImageToCustomAlbum
{
// 将图片保存到相机胶卷
NSError *error = nil;
__block PHObjectPlaceholder *placeholder = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
placeholder = [PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset;
} error:&error];
if (error) {
NSLog(@"保存失败");
}
// 获取自定义相册
PHAssetCollection *createdCollection = [self createCustomAssetCollection]; // 将图片保存到自定义相册
/**
* 必须通过中间类,PHAssetCollectionChangeRequest 来完成
* 步骤:1.首先根据相册获取 PHAssetCollectionChangeRequest 对象
* 2.然后根据 PHAssetCollectionChangeRequest 来添加图片
* 这一步的实现有两个思路:1.通过上面的占位 asset 的标识来获取 相机胶卷中的 asset
* 然后,将 asset 添加到 request 中
* 2.直接将 占位 asset 添加到 request 中去也是可行的
*/
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:createdCollection];
// [request addAssets:@[placeholder]]; 下面的方法可以将最新保存的图片设置为封面
[request insertAssets:@[placeholder] atIndexes:[NSIndexSet indexSetWithIndex:]];
} error:&error];
if (error) {
NSLog(@"保存失败");
} else {
NSLog(@"保存成功");
}
}

最终代码

最终代码:整理后可用于项目中

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 获取 当前 App 对 phots 的访问权限
PHAuthorizationStatus OldStatus = [PHPhotoLibrary authorizationStatus]; // 检查访问权限 当前 App 对相册的检查权限
/**
* PHAuthorizationStatus
* PHAuthorizationStatusNotDetermined = 0, 用户还未决定
* PHAuthorizationStatusRestricted, 系统限制,不允许访问相册 比如家长模式
* PHAuthorizationStatusDenied, 用户不允许访问
* PHAuthorizationStatusAuthorized 用户可以访问
* 如果之前已经选择过,会直接执行 block,并且把以前的状态传给你
* 如果之前没有选择过,会弹框,在用户选择后调用 block 并且把用户的选择告诉你
* 注意:该方法的 block 在子线程中运行 因此,弹框什么的需要回到主线程执行
*/
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if (status == PHAuthorizationStatusAuthorized) {
// [self cSaveToCameraRoll];
// [self photoSaveToCameraRoll];
// [self fetchCameraRoll];
// [self createCustomAssetCollection];
// [self createdAsset];
// [self saveImageToCustomAlbum2];
[self saveImageToCustomAlbum1];
} else if (OldStatus != PHAuthorizationStatusNotDetermined && status == PHAuthorizationStatusDenied) {
// 用户上一次选择了不允许访问 且 这次又点击了保存 这里可以适当提醒用户允许访问相册
}
});
}]; } #pragma mark - 将图片保存到自定义相册中 第一种写法 比较规范
#pragma mark - - (void)saveImageToCustomAlbum1
{
// 获取保存到相机胶卷中的图片
PHAsset *createdAsset = [self createdAssets].firstObject;
if (createdAsset == nil) {
NSLog(@"保存图片失败");
}
// 获取自定义相册
PHAssetCollection *createdCollection = [self createCustomAssetCollection];
if (createdCollection == nil) {
NSLog(@"创建相册失败");
} NSError *error = nil;
// 将图片保存到自定义相册
/**
* 必须通过中间类,PHAssetCollectionChangeRequest 来完成
* 步骤:1.首先根据相册获取 PHAssetCollectionChangeRequest 对象
* 2.然后根据 PHAssetCollectionChangeRequest 来添加图片
* 这一步的实现有两个思路:1.通过上面的占位 asset 的标识来获取 相机胶卷中的 asset
* 然后,将 asset 添加到 request 中
* 2.直接将 占位 asset 添加到 request 中去也是可行的
*/
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *request = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:createdCollection];
// [request addAssets:@[placeholder]];
[request insertAssets:@[createdAsset] atIndexes:[NSIndexSet indexSetWithIndex:]];
} error:&error];
if (error) {
NSLog(@"保存失败");
} else {
NSLog(@"保存成功");
}
} #pragma mark - 获取保存到【相机胶卷】的图片
#pragma mark - - (PHFetchResult<PHAsset *> *)createdAssets
{
// 将图片保存到相机胶卷
NSError *error = nil;
__block NSString *assetID = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
assetID = [PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image].placeholderForCreatedAsset.localIdentifier;
} error:&error];
if (error) return nil;
return [PHAsset fetchAssetsWithLocalIdentifiers:@[assetID] options:nil];
} #pragma mark - 使用 photo 框架创建自定义名称的相册 并获取自定义到自定义相册
#pragma mark - - (PHAssetCollection *)createCustomAssetCollection
{
// 获取 app 名称
NSString *title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey]; NSError *error = nil; // 查找 app 中是否有该相册 如果已经有了 就不再创建
/**
* 参数一 枚举:
* PHAssetCollectionTypeAlbum = 1, 用户自定义相册
* PHAssetCollectionTypeSmartAlbum = 2, 系统相册
* PHAssetCollectionTypeMoment = 3, 按时间排序的相册
*
* 参数二 枚举:PHAssetCollectionSubtype
* 参数二的枚举有非常多,但是可以根据识别单词来找出我们想要的。
* 比如:PHAssetCollectionTypeSmartAlbum 系统相册 PHAssetCollectionSubtypeSmartAlbumUserLibrary 用户相册 就能获取到相机胶卷
* PHAssetCollectionSubtypeAlbumRegular 常规相册
*/
PHFetchResult<PHAssetCollection *> *result = [PHAssetCollection fetchAssetCollectionsWithType:(PHAssetCollectionTypeAlbum)
subtype:(PHAssetCollectionSubtypeAlbumRegular)
options:nil]; for (PHAssetCollection *collection in result) {
if ([collection.localizedTitle isEqualToString:title]) { // 说明 app 中存在该相册
return collection;
}
} /** 来到这里说明相册不存在 需要创建相册 **/
__block NSString *createdCustomAssetCollectionIdentifier = nil;
// 创建和 app 名称一样的 相册
/**
* 注意:这个方法只是告诉 photos 我要创建一个相册,并没有真的创建
* 必须等到 performChangesAndWait block 执行完毕后才会
* 真的创建相册。
*/
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
PHAssetCollectionChangeRequest *collectionChangeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title];
/**
* collectionChangeRequest 即使我们告诉 photos 要创建相册,但是此时还没有
* 创建相册,因此现在我们并不能拿到所创建的相册,我们的需求是:将图片保存到
* 自定义的相册中,因此我们需要拿到自己创建的相册,从头文件可以看出,collectionChangeRequest
* 中有一个占位相册,placeholderForCreatedAssetCollection ,这个占位相册
* 虽然不是我们所创建的,但是其 identifier 和我们所创建的自定义相册的 identifier
* 是相同的。所以想要拿到我们自定义的相册,必须保存这个 identifier,等 photos app
* 创建完成后通过 identifier 来拿到我们自定义的相册
*/
createdCustomAssetCollectionIdentifier = collectionChangeRequest.placeholderForCreatedAssetCollection.localIdentifier;
} error:&error]; // 这里 block 结束了,因此相册也创建完毕了
if (error) return nil; return [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createdCustomAssetCollectionIdentifier] options:nil].firstObject;
}

从相册中选取图片

从相册里面选择图片到 App 中

选择单张图片
   UIImagePickerController
   AssetsLibrary 框架
   Photos 框架 选择多张图片(图片数量 >= 2)
   AssetsLibrary 框架
   Photos 框架

利用照相机拍一张照片到 App

使用 UIImagePickerController (界面已经写好了)
AVCaptureSession (AVFoundation框架下)

使用第三方框架获取多张照片

这里推荐大家使用 CTAssetsPickerController 第三方框架。

地址如下:CTAssetsPickerController

https://github.com/chiunam/CTAssetsPickerController

如果您的项目是使用 pod 来管理第三方库的,直接
pod ‘CTAssetsPickerController’ 就可以了。其内部还依赖一个叫做 PureLayout 的库。

具体信息见代码:多图片访问

https://github.com/lizhaoLoveIT/assetPhotoPicker

iOS 下的相册与图片处理的更多相关文章

  1. ios中摄像头/相册获取图片压缩图片上传服务器方法总结

    本文章介绍了关于ios中摄像头/相册获取图片,压缩图片,上传服务器方法总结,有需要了解的同学可以参考一下下.     这几天在搞iphone上面一个应用的开发,里面有需要摄像头/相册编程和图片上传的问 ...

  2. ios中摄像头/相册获取图片,压缩图片,上传服务器方法总结

    相册 iphone的相册包含摄像头胶卷+用户计算机同步的部分照片.用户可以通过UIImagePickerController类提供的交互对话框来从相册中选择图像.但是,注意:相册中的图片机器路径无法直 ...

  3. ios下app内嵌h5页面是video适配问题

    ios下做新闻详情用h5页面实现然后打包到app中,其中新闻详情页会有视频,安卓下video的poster可以做到适应video大小,但是ios下会按照poster图片大小将video等比撑大,但是视 ...

  4. IOS研究院之打开照相机与本地相册选择图片(六)

    原创文章如需转载请注明:转载自雨松MOMO程序研究院本文链接地址:IOS研究院之打开照相机与本地相册选择图片(六) Hello 大家好 IOS的文章好久都木有更新了,今天更新一篇哈. 这篇文章主要学习 ...

  5. iOS 使用AFN 进行单图和多图上传 摄像头/相册获取图片,压缩图片

    图片上传时必要将图片进行压缩,不然会上传失败 首先是同系统相册选择图片和视频.iOS系统自带有UIImagePickerController,可以选择或拍摄图片视频,但是最大的问题是只支持单选,由于项 ...

  6. iOS相册中图片按照时间排序

    ios相册默认是按照时间从过去到现在排列,图片顺序有正序和逆序,group可以用以下方法来选择顺序 /** @param NSIndexSet 需要获取的相册中图片范围 @param NSEnumer ...

  7. 基于jQuery向下弹出遮罩图片相册

    今天给大家分享一款基于jQuery向下弹出遮罩图片相册.单击相册图片时,一个遮罩层从上到下动画出现.然后弹出显示图片.这款插件适用浏览器:IE8.360.FireFox.Chrome.Safari.O ...

  8. iOS UIWebView 中 js调用OC 打开相册 获取图片, OC调用js 将图片加载到html上

    线上html <!DOCTYPE html> <html> <head> <title>HTML中用JS调用OC方法</title> < ...

  9. IOS下拉放大图片

    代码地址如下:http://www.demodashi.com/demo/11623.html 一.实现效果图 现在越来越多的APP中存在下拉放大图片的效果,今天贡献一下我的实现这种方法的原理,和我遇 ...

随机推荐

  1. [转] 翻译-高质量JavaScript代码书写基本要点 ---张鑫旭

    by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.com/wordpress/?p=1173 原文作者:St ...

  2. 分享29个超赞的响应式Web设计

    原文自:http://www.csdn.net/article/2013-01-16/2813678-responsive-design-websites 最近几年,响应式Web设计不断印入人们眼帘, ...

  3. 关于org.openqa.selenium.ElementNotVisibleException

    最近在使用Selenium,编写最简单的百度search脚本,结果使用name来定位元素抛出了如下exception: 在定位百度的输入框,使用By.name()定位失败,但是使用By.id()和By ...

  4. 转:.NET中使用Redis (一)

    原文来自于:http://blog.jobbole.com/83821/ 原文出处: 寒江独钓   欢迎分享原创到伯乐头条 Redis是一个用的比较广泛的Key/Value的内存数据库,新浪微博.Gi ...

  5. 关于git服务器的搭建

    Git 服务器可搭建在多个系统平台上. 本篇以 Windows 和 Ubuntu 系统为例,简单介绍 Git 服务器的构建. 最后使用 eclipse 的 egit 插件和 git clone 这两种 ...

  6. ubuntu 解压rar

    Ubuntu下解压rar文件的方法 一般通过默认安装的ubuntu是不能解压rar文件的,只有在安装了rar解压工具之后,才可以解压.其实在ubuntu下安装rar解压工具是非常简单的,只需要两个步骤 ...

  7. 关于Unity3D中的版本管理 .

    关于Unity3D中的版本管理 使用Unity3D也有一段时间了,由于团队一直使用SVN进行版本管理,现总结一下: (1) Unity3D的二进制资源必须加锁进行版本控制,因为它没办法merge: ( ...

  8. hdu 5074 Hatsune Miku

    http://acm.hdu.edu.cn/showproblem.php?pid=5074 题意:给你一个的矩阵score[i][j],然后给你一个数列,数列中有一些是-1,代表这个数可以换成1~m ...

  9. Apache HTTPServer与JBoss/Tomcat的整合与请求分发

    http://www.blogjava.net/supercrsky/archive/2008/12/24/248143.html

  10. #include< >和#include""的区别

    Answer 1:#include 会将指定文件的内容插入到源程序文件中.当使用的格式时,编译器会从环境变量INCLUDE所指定的路径中寻找file-name 文件,如果没有定义INCLUDE,C 编 ...