保存图片到自定义相册中

  • 大部分app都会将照片保存到自己的相册
  • 照片应用中的具体展示
Snip20161027_3.png
  • 注意:如果你现在使用的是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;
}

最终方案

    1. 首先判断当前app访问相册的授权状态
  • 2 .根据不同的授权状态执行不同的操作
    • 如果已经授权,那么直接保存
    • 如果没有授权,提醒用户授权
    1. 保存图片到自定义相册中
// 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;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容