- 大部分app都会将照片保存到自己的相册
- 在
照片
应用中的具体展示
注意:如果你现在使用的是Xocde8,那么在请求访问用户的隐私数据时(比如:相机,相册,联系人等), 需要在Info.plist配置相关信息,不然会直接crash掉.
如果是在Xcode8中编译的话,记得配置好下面的键值对
<key>NSPhotoLibraryUsageDescription</key>
<string>"App名称(写你需要的名称)"需要您的同意,才能访问相册</string>
保存照片到系统的相册中(非常easy)
/**
将照片写入到系统相册中
@param image 需要保存的照片
@param completionTarget 保存完成时的消息调用者
@param completionSel 保存完成时需要调用的方法(必须使用系统提供的方法)
*/
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSel;
// 上面的方法中必须使用这个方法,来作为SEL类型的参数
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
- 完整代码
// 点击保存按钮, 将图片存入到系统相册中
- (void)save
{
UIImageWriteToSavedPhotosAlbum(_imageView.image, self, @selector(imagePickerController:didFinishPickingMediaWithInfo:), nil);
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
if (error) {
NSLog(@"提醒用户保存失败--%@", error.description);
} else {
NSLog(@"提醒用户保存成功");
}
}
保存照片到系统相册(稍微复杂了那么一丢丢)
Photos/Photos.h框架中我们需要使用的类的简单介绍
- 首先,想要创建我们自己的相册,需要导入一个框架
Photos/Photos.h
, 使用这个框架中的类. - 我们来看一下实现该功能需要的几个类的介绍
PHPhotoLibrary(相簿类)
You use the shared photo library object to get the user' s permission for your app to access Photos content, to perform changes to assets and collections, and to register for update messages sent when the Photos library changes
使用这个类的一个单粒对象,用于得到用户的许可来让你的app访问手机的相簿里面的内容,也可以用于修改相簿中的图片和相册,也可以通过注册来监听相簿内容的改变.
PHAsset(资源类,图片,视频,或者动态照片资源)
A representation of an image, video or Live Photo in the Photos library.
在相簿中代表一张图片, 一段视频, 或者一张动态照片
PHAssetChangeRequest(操作资源的请求类)
A request to create, delete, change metadata for, or edit the content of a Photos asset, for use in a photo library change block.
一个请求用于创建,删除和修改图片的.或者用于编辑图片,必须在PHPhotoLibrary这个类的对象方法- (void)performChanges:(dispatch_block_t)changeBlock completionHandler:(nullable void(^)(BOOL success, NSError *__nullable error))completionHandler 方法中使用
PHAssetCollection(相册类)
A representation of a Photos asset grouping, such as a moment, user-created album, or smart album.
在相簿中用于表示一组图片,例如系统根据时间创建的相册,以及用户自己创建的相册,以及智能相册
PHAssetCollectionChangeRequest(操作相册的请求类)
A request to create, delete, or modify a Photos asset collection, for use in a photo library change block.
一个请求对象,用于创建,删除和修改一个相册,必须在`PHPhotoLibrary`这个类的对象方法- (void)performChanges:(dispatch_block_t)changeBlock completionHandler:(nullable void(^)(BOOL success, NSError *__nullable error))completionHandler 方法中使用
PHObjectPlaceholder(资源的代理类,等同于资源对象)
A read-only proxy representing a Photos asset or collection object yet to be created by a change request.
一个只读属性,通过一个改变请求(changeReques)来创建的,用于表示一张图片对象或者一个相册对象的代理
PHFetchResult(用于获取资源或者相册的类)
An ordered list of assets or collections returned from a Photos fetch method.
通过一个fetch开头的方法获取的一组有序的图片或者相册(可以理解为一个数组)
- 好了,科普就到这里,看不懂也么有关系
思路
- 创建一个相册,并得到一个可以操作(创建,删除,修改)相册的
PHAssetCollectionChangeRequest
的实例对象 - 将图片存入到系统相册中,并获取一个可以操作图片的
PHAssetChangeRequest
的实例对象 - 最后通过
PHAssetChangeRequest
的对象来创建一个PHObjectPlaceholder
的一个代理对象,然后将这个代理对象添加到自定义的相册中,这样通过这个代理对象我们可以间接访问到这张图片.
代码思路
- 首先要将照片写入到系统相册,我们首先需要创建一个自己的相册,这里需要使用可以创建相册的类,通过上面的科普知识,可以知道是使用
PHAssetCollectionChangeRequest
这个类
// 通过该方法创建一个自定义的相册
PHAssetCollectionChangeRequest *assetCollectionChangRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:@"自定义相册名称"];
- 其次,我们要将需要保存的照片先存入到系统相册中,并创建一个
PHAssetChangeRequest
请求- 必须先将图片存入到系统相册中,然后才可以来操作这张图片,将其存到我们自定义的相簿中.
- 可以简单的通过一个方法来创建这个请求对象
-
creationRequestForAssetFromImage
: 该方法的作用就是将传入的图片存入到系统相册中,并返回一个PHAssetChangeRequest
对象
// 传入一个UIImage的图片
PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:_imageView.image];
- 最后,将图片存入到我们自定义的相册中
- 首先根据图片的改变请求对象
assetChangeRequest
来创建一个占位的图片图像,也就是这张图片的代理 - 然后,将这个占位的图片对象存入到自定相册中,这样我们就可以通过这个代理对象来访问存入到系统相册中的那张图片.
- 首先根据图片的改变请求对象
// 创建占位图片
PHObjectPlaceholder *assetPlaceholder = [assetChangeRequest placeholderForCreatedAsset];
// 存入占位图片到自定义相册中
[assetCollectionChangRequest addAssets:@[assetPlaceholder]];
-
上面的步骤仅仅用于演示大致过程,其中有两点问题
- 首先,我们不知道当前用户的授权状态,若用户没有授权,那么点击保存以后,没有任何提示,会印象用户体验
- 其次,自定义的相册,我们只需要创建一个就行了.所以需要判断,来保证只会创建一次相册
-
判断当前的授权状态(通过
PHPhotoLibrary
这个类)- 首先根据
[PHPhotoLibrary authorizationStatus]
这个方法来获取当前的授权状态,返回值是一个PHAuthorizationStatus
枚举类型
typedef NS_ENUM(NSInteger, PHAuthorizationStatus) { PHAuthorizationStatusNotDetermined = 0, 授权状态未决定 PHAuthorizationStatusRestricted, 未授权 -- 可能用户没有权利授权,例如家长控制 PHAuthorizationStatusDenied, 未授权 -- 用户明确的拒绝应用访问相册 PHAuthorizationStatusAuthorized 已授权
- 首先根据
}
```
- 获取到当前的授权状态,根据不同的状态进行处理
```objc
// 获取当前的授权状态
PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
if (authorizationStatus == PHAuthorizationStatusNotDetermined) {
// 如果没有授权,那么我们需要向用户请求授权
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
// 授权成功, 直接保存图片
} else {
// 授权失败,我们需要提示用户,请到 设置 -> 应用名 -> 相册 -> ON 来开启相册授权
}
}];
} else if (authorizationStatus == PHAuthorizationStatusAuthorized) {
// 已经授权成功,直接保存图片
} else {
// 没有授权
// 我们需要提醒用户, 请到 设置 -> 应用名 -> 相册 -> ON 来开启相册授权
}
```
- 保证相册只会创建一次, 这里我们抽取了一个方法,专门来用于判断相簿中是否已经存在了指定名称的相册
customAlbumForTitle:
- 获取相簿中所有自定义创建的相册
- 返回值是
PHFetchResult
类的实例对象,由于它是遵守NSFastEnumeration
协议的,所以我们可以当做是一个数组来遍历它所有的元素
- 返回值是
- 遍历相册,判断是否有同名的相册
- 获取相簿中所有自定义创建的相册
// 在外界,我们只需要调用该方法,判断是否有值,即可判断当前的相册是否存在.
// 获取指定名称的相册
- (PHAssetCollection *)customAlbumForTitle:(NSString *)albumTitle
{
// 1.获取所有自定义的相册
PHFetchResult *result = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
// 2.遍历相册,判断是否有同名的相册
for (PHAssetCollection *assetCollection in result) {
if ([assetCollection.localizedTitle isEqualToString:JGPhotoAlbumTitle]) {
// 2.1 有同名相册,直接将这个相册返回
return assetCollection;
}
}
return nil;
}
最终方案
- 首先判断当前app访问相册的授权状态
- 2 .根据不同的授权状态执行不同的操作
- 如果已经授权,那么直接保存
- 如果没有授权,提醒用户授权
- 保存图片到自定义相册中
// SVProgressHUD是一个第三方框架,比较好用
// 该方法为点击保存图片按钮的时候调用
// 主要实现了判断当前授权状态,并执行不同操作
- (IBAction)save:(id)sender {
// 获取当前的授权状态
PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
if (authorizationStatus == PHAuthorizationStatusNotDetermined) { // 授权状态为确定
// 请求授权
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
[self savePhotos];
} else {
[SVProgressHUD setMinimumDismissTimeInterval:1.0];
[SVProgressHUD showErrorWithStatus:@"请到 设置 -> 应用名 -> 相册 -> ON 来开启相册授权"];
}
}];
} else if (authorizationStatus == PHAuthorizationStatusAuthorized) {
[self savePhotos];
} else {
[SVProgressHUD setMinimumDismissTimeInterval:1.0];
[SVProgressHUD showErrorWithStatus:@"请到 设置 -> 应用名 -> 相册 -> ON 来开启相册授权"];
}
}
// 保存图片到自定义相册的具体实现
- (void)savePhotos
{
// 将图片保存到自定义的相册 PHPhotoLibrary PHAssetCollectionChangeRequest
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 1.自定义相册
// 1.1 获取指定名称的相册对象
PHAssetCollection *assetCollection = [self customAlbumForTitle:JGPhotoAlbumTitle];
PHAssetCollectionChangeRequest *assetCollectionChangRequest;
// 1.2如果存在, 那么直接拿到这个相册,创建一个修改相册请求
if (assetCollection) {
assetCollectionChangRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
} else {
// 1.3如果没有,自己创建一个相册,并创建修改相册请求
assetCollectionChangRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:JGPhotoAlbumTitle];
}
// 2.将照片保存到系统的相册中,并创建一个改变图片的请求对象
PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:self.imageView.image];
// 3.将系统中的相册保存到自定义相册中
PHObjectPlaceholder *assetPlaceholder = [assetChangeRequest placeholderForCreatedAsset];
[assetCollectionChangRequest addAssets:@[assetPlaceholder]];
} completionHandler:^(BOOL success, NSError * _Nullable error) { // 当操作执行完毕
if (error) { // 保存不成功
[SVProgressHUD setMinimumDismissTimeInterval:1.0];
[SVProgressHUD showErrorWithStatus:@"保存失败"];
} else { // 保存成功
[SVProgressHUD setMinimumDismissTimeInterval:1.0];
[SVProgressHUD showSuccessWithStatus:@"保存成功"];
}
}];
}
// 获取指定名称的相册
- (PHAssetCollection *)customAlbumForTitle:(NSString *)albumTitle
{
// 首先判断当前的相册是否已经创建
PHFetchResult *result = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
for (PHAssetCollection *assetCollection in result) {
if ([assetCollection.localizedTitle isEqualToString:JGPhotoAlbumTitle]) {
return assetCollection;
}
}
return nil;
}