iOS 下的相冊與圖片處理


iOS 下的相冊與圖片處理 

需求

很多公司項目中都會使用到相冊,以及相機,保存圖片,從相冊中選取圖片等等操作。本文將詳細介紹該功能如何實現優化,以及使用一些優秀的第三方庫來輔助完成我們的需求。

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

/* 
 A PHAssetCreationRequest object, used within a photo library 
 change block, constructs a new photo or video asset from data 
 resources, and adds it to the Photos library.

 PHAssetCreationRequest 對象用於在照片庫的增刪改操作中,創建一個新的 
 image 和 video asset 從 data resources 中,然后將其加入 photos 
 library 中。
 */

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:0]];
    } 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:0]];
    } 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

文/Ammar(簡書作者)
原文鏈接:http://www.jianshu.com/p/091e0198ae61


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM