相冊(cè)管理

圖片視頻管理 AssetsLibrary

在ios8之前 ,只能使用AssetsLibrary 來(lái)訪問設(shè)備的照片庫(kù) 。
ios8之后,蘋果提供了PhotoKit 框架 。

需要注意,在ios中 ,照片庫(kù)中既包含 圖片 ,也包含 視頻 。

--

AssetsLibrary 組成介紹

  • AssetsLibrary: 代表整個(gè)設(shè)備中的資源庫(kù)(照片庫(kù)),通過(guò)
  • AssetsLibrary 可以獲取和包括設(shè)備中的照片和視頻
  • ALAssetsGroup: 映射照片庫(kù)中的一個(gè)相冊(cè),通過(guò)
  • ALAssetsGroup 可以獲取某個(gè)相冊(cè)的信息,相冊(cè)下的資源,同時(shí)也可以對(duì)某個(gè)相冊(cè)添加資源。
  • ALAsset: 映射照片庫(kù)中的一個(gè)照片或視頻,通過(guò) ALAsset 可以獲取某個(gè)照片或視頻的詳細(xì)信息,或者保存照片和視頻。
  • ALAssetRepresentation: ALAssetRepresentation 是對(duì) ALAsset 的封裝(但不是其子類),可以更方便地獲取 ALAsset 中的資源信息,每個(gè) ALAsset 都有至少有一個(gè) ALAssetRepresentation 對(duì)象,可以通過(guò) defaultRepresentation 獲取。而例如使用系統(tǒng)相機(jī)應(yīng)用拍攝的 RAW + JPEG 照片,則會(huì)有兩個(gè) ALAssetRepresentation,一個(gè)封裝了照片的 RAW 信息,另一個(gè)則封裝了照片的 JPEG 信息。

主要需要掌握的方法就是從相冊(cè)加載圖片

  • 首先是要檢查 App 是否有照片操作授權(quán):
NSString *tipTextWhenNoPhotosAuthorization; // 提示語(yǔ)
// 獲取當(dāng)前應(yīng)用對(duì)照片的訪問授權(quán)狀態(tài)
ALAuthorizationStatus authorizationStatus = [ALAssetsLibrary authorizationStatus];
// 如果沒有獲取訪問授權(quán),或者訪問授權(quán)狀態(tài)已經(jīng)被明確禁止,則顯示提示語(yǔ),引導(dǎo)用戶開啟授權(quán)
if (authorizationStatus == ALAuthorizationStatusRestricted || authorizationStatus == ALAuthorizationStatusDenied) {
    NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
    tipTextWhenNoPhotosAuthorization = [NSString stringWithFormat:@"請(qǐng)?jiān)谠O(shè)備的\"設(shè)置-隱私-照片\"選項(xiàng)中,允許%@訪問你的手機(jī)相冊(cè)", appName];
    // 展示提示語(yǔ)
}
  • 獲取相冊(cè)列表
_assetsLibrary = [[ALAssetsLibrary alloc] init];
_albumsArray = [[NSMutableArray alloc] init];
[_assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
    if (group) {
        [group setAssetsFilter:[ALAssetsFilter allPhotos]];
        if (group.numberOfAssets > 0) {
            // 把相冊(cè)儲(chǔ)存到數(shù)組中,方便后面展示相冊(cè)時(shí)使用
            [_albumsArray addObject:group];
        }
    } else {
        if ([_albumsArray count] > 0) {
            // 把所有的相冊(cè)儲(chǔ)存完畢,可以展示相冊(cè)列表
        } else {
            // 沒有任何有資源的相冊(cè),輸出提示
        }
    }
} failureBlock:^(NSError *error) {
    NSLog(@"Asset group not found!\n");
}];

這里需要的主意

1 iOS 中允許相冊(cè)為空,即相冊(cè)中沒有任何資源,如果不希望獲取空相冊(cè),則需要像上面的代碼中那樣手動(dòng)過(guò)濾
2 ALAssetsGroup 有一個(gè) setAssetsFilter 的方法,可以傳入一個(gè)過(guò)濾器,控制只獲取相冊(cè)中的照片或只獲取視頻。一旦設(shè)置過(guò)濾,ALAssetsGroup 中資源列表和資源數(shù)量的獲取也會(huì)被自動(dòng)更新。
3 整個(gè) AssetsLibrary 中對(duì)相冊(cè)、資源的獲取和保存都是使用異步處理(Asynchronous),這是考慮到資源文件體積相當(dāng)比較大(還可能很大)。例如上面的遍歷相冊(cè)操作,相冊(cè)的結(jié)果使用 block 輸出,如果相冊(cè)遍歷完畢,則最后一次輸出的 block 中的 group 參數(shù)值為 nil。而 stop 參數(shù)則是用于手工停止遍歷,只要把 *stop 置 YES,則會(huì)停止下一次的遍歷。關(guān)于這一點(diǎn)常常會(huì)引起誤會(huì),所以需要注意。

  • 接下來(lái)是獲取相冊(cè)中的資源:
_imagesAssetArray = [[NSMutableArray alloc] init];
[assetsGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
    if (result) {
        [_imagesAssetArray addObject:result];
    } else {
        // result 為 nil,即遍歷相片或視頻完畢,可以展示資源列表
    }
}];

跟遍歷相冊(cè)的過(guò)程類似,遍歷相片也是使用一系列的異步方法,其中上面的方法所輸出的 block 中,除了 result 參數(shù)表示資源信息,stop 用于手工停止遍歷外,還提供了一個(gè) index 參數(shù),這個(gè)參數(shù)表示資源的索引。一般來(lái)說(shuō),展示資源列表都會(huì)使用縮略圖(result.thumbnail),因此即使資源很多,遍歷資源的速度也會(huì)相當(dāng)快。但如果確實(shí)需要加載資源的高清圖或者其他耗時(shí)的處理,則可以利用上面的 index 參數(shù)和 stop 參數(shù)做一個(gè)分段拉取資源。例如:

NSUInteger _targetIndex; // index 目標(biāo)值,拉取資源直到這個(gè)值就手工停止拉取
NSUInteger _currentIndex; // 當(dāng)前 index,每次拉取資源時(shí)從這個(gè)值開始
 
_targetIndex = 50;
_currentIndex = 0;
 
- (void)loadAssetWithAssetsGroup:(assetsGroup *)assetsGroup {
    [assetsGroup enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:_currentIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
        _currentIndex = index;
        if (index > _targetIndex) {
            // 拉取資源的索引如果比目標(biāo)值大,則停止拉取
            *stop = YES;
        } else {
            if (result) {
                [_imagesAssetArray addObject:result];
            } else {
                // result 為 nil,即遍歷相片或視頻完畢
            }
        }
    }];
}
 
// 之前拉取的數(shù)據(jù)已經(jīng)顯示完畢,需要展示新數(shù)據(jù),重新調(diào)用 loadAssetWithAssetsGroup
  • 最后是獲取圖片的詳情
// 獲取資源圖片的詳細(xì)資源信息,其中 imageAsset 是某個(gè)資源的 ALAsset 對(duì)象
ALAssetRepresentation *representation = [imageAsset defaultRepresentation];
// 獲取資源圖片的 fullScreenImage
UIImage *contentImage = [UIImage imageWithCGImage:[representation fullScreenImage]];

對(duì)于一個(gè) ALAssetRepresentation,里面包含了圖片的多個(gè)版本。最常用的是 fullResolutionImage 和 fullScreenImage。fullResolutionImage 是圖片的原圖,通過(guò) fullResolutionImage 獲取的圖片沒有任何處理,包括通過(guò)系統(tǒng)相冊(cè)中“編輯”功能處理后的信息也沒有被包含其中,因此需要展示“編輯”功能處理后的信息,使用 fullResolutionImage 就比較不方便,另外 fullResolutionImage 的拉取也會(huì)比較慢,在多張 fullResolutionImage 中切換時(shí)能明顯感覺到圖片的加載過(guò)程。因此這里建議獲取圖片的 fullScreenImage,它是圖片的全屏圖版本,這個(gè)版本包含了通過(guò)系統(tǒng)相冊(cè)中“編輯”功能處理后的信息,同時(shí)也是一張縮略圖,但圖片的失真很少,缺點(diǎn)是圖片的尺寸是一個(gè)適應(yīng)屏幕大小的版本,因此展示圖片時(shí)需要作出額外處理,但考慮到加載速度非??斓脑颍ㄔ诙鄰垐D片之間切換感受不到圖片加載耗時(shí)),仍建議使用 fullScreenImage。

系統(tǒng)相冊(cè)的處理過(guò)程大概也是如上,可以看出,在整個(gè)過(guò)程中并沒有使用到圖片的 fullResolutionImage,從相冊(cè)列表展示到最終查看資源,都是使用縮略圖,這也是 iOS 相冊(cè)加載快的一個(gè)重要原因。

--

補(bǔ)充

獲取原圖
CGImageRef fullResolutionImageRef = [[(ALAsset *)asset defaultRepresentation] fullResolutionImage];
//        // 通過(guò) fullResolutionImage 獲取到的的高清圖實(shí)際上并不帶上在照片應(yīng)用中使用“編輯”處理的效果,需要額外在 AlAssetRepresentation 中獲取這些信息
        NSString *adjustment = [[[(ALAsset *)asset defaultRepresentation] metadata] objectForKey:@"AdjustmentXMP"];
        if (adjustment) {
            // 如果有在照片應(yīng)用中使用“編輯”效果,則需要獲取這些編輯后的濾鏡,手工疊加到原圖中
            NSData *xmpData = [adjustment dataUsingEncoding:NSUTF8StringEncoding];
            CIImage *tempImage = [CIImage imageWithCGImage:fullResolutionImageRef];
            
            NSError *error;
            NSArray *filterArray = [CIFilter filterArrayFromSerializedXMP:xmpData
                                                         inputImageExtent:tempImage.extent
                                                                    error:&error];
            CIContext *context = [CIContext contextWithOptions:nil];
            if (filterArray && !error) {
                for (CIFilter *filter in filterArray) {
                    [filter setValue:tempImage forKey:kCIInputImageKey];
                    tempImage = [filter outputImage];
                }
                fullResolutionImageRef = [context createCGImage:tempImage fromRect:[tempImage extent]];
            }
        }
        // 生成最終返回的 UIImage,同時(shí)把圖片的 orientation 也補(bǔ)充上去
        resultImage = [UIImage imageWithCGImage:fullResolutionImageRef
                                          scale:[[asset defaultRepresentation] scale]
                                    orientation:(UIImageOrientation)[[asset defaultRepresentation] orientation]];

帶一個(gè)block參數(shù) 作為輸入值 ,函數(shù)之行到block 。就會(huì)調(diào)用block 。也就是回調(diào)block 。

    `[[XMNPhotoManager sharedManager] getOriginImageWithAsset:self.asset completionBlock:^(UIImage *image){
    resultImage = image;
}];

`

- (void)getOriginImageWithAsset:(id _Nonnull)asset completionBlock:(void(^_Nonnull)(UIImage * _Nullable image))completionBlock;
這段代碼就是manger 之行方法,會(huì)調(diào)用block (這個(gè)block 需要一個(gè)image輸入值)。

manger 就是 用一個(gè)image 來(lái)調(diào)用block 。最后block 就回調(diào)他自己的代碼塊 { resultImage = image; }];
實(shí)現(xiàn)了image 從 manger 到其它類的 異步傳遞 。

相冊(cè)管理 PhotoKit

  • PHAsset: 代表照片庫(kù)中的一個(gè)資源,跟 ALAsset 類似,通過(guò) PHAsset 可以獲取和保存資源
  • PHFetchOptions: 獲取資源時(shí)的參數(shù),可以傳 nil,即使用系統(tǒng)默認(rèn)值
  • PHFetchResult: 表示一系列的資源集合,也可以是相冊(cè)的集合
  • PHAssetCollection: 表示一個(gè)相冊(cè)或者一個(gè)時(shí)刻,或者是一個(gè)「智能相冊(cè)(系統(tǒng)提供的特定的一系列相冊(cè),例如:最近刪除,視頻列表,收藏等等)
  • PHImageManager: 用于處理資源的加載,加載圖片的過(guò)程帶有緩存處理,可以通過(guò)傳入一個(gè) PHImageRequestOptions 控制資源的輸出尺寸等規(guī)格
  • PHImageRequestOptions: 如上面所說(shuō),控制加載圖片時(shí)的一系列參數(shù)
// 列出所有相冊(cè)智能相冊(cè)
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
 
// 列出所有用戶創(chuàng)建的相冊(cè)
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
 
// 獲取所有資源的集合,并按資源的創(chuàng)建時(shí)間排序
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
 
// 在資源的集合中獲取第一個(gè)集合,并獲取其中的圖片
PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
PHAsset *asset = assetsFetchResults[0];
[imageManager requestImageForAsset:asset
                         targetSize:SomeSize
                        contentMode:PHImageContentModeAspectFill
                            options:nil
                      resultHandler:^(UIImage *result, NSDictionary *info) {
                           
                          // 得到一張 UIImage,展示到界面上
                           
                      }];
  • 從 AssetsLibrary 中獲取數(shù)據(jù),無(wú)論是相冊(cè),還是資源,本質(zhì)上都是使用枚舉的方式,遍歷照片庫(kù)取得相應(yīng)的數(shù)據(jù)。而 PhotoKit 則是通過(guò)傳入?yún)?shù),直接獲取相應(yīng)的數(shù)據(jù),因而效率會(huì)提高不少。
  • 在 AssetsLibrary 中,相冊(cè)和資源是對(duì)應(yīng)不同的對(duì)象(ALAssetGroup 和 ALAsset),因此獲取相冊(cè)和獲取資源是兩個(gè)完全沒有關(guān)聯(lián)的接口。而 PhotoKit 中則有 PHFetchResult 這個(gè)可以統(tǒng)一儲(chǔ)存相冊(cè)或資源的對(duì)象,因此處理相冊(cè)和資源時(shí)也會(huì)比較方便。
  • PhotoKit 返回資源結(jié)果時(shí),同時(shí)返回了資源的元數(shù)據(jù),獲取元數(shù)據(jù)在 AssetsLibrary 中是很難辦到的一件事。同時(shí)通過(guò) PHAsset,開發(fā)者還能直接獲取資源是否被收藏(favorite)和隱藏(hidden),拍攝圖片時(shí)是否開啟了 HDR 或全景模式,甚至能通過(guò)一張連拍圖片獲取到連拍圖片中的其他圖片。這也是文章開頭說(shuō)的,PhotoKit 能更好地與設(shè)備照片庫(kù)接入的一個(gè)重要因素。

一個(gè)很棒的圖片選擇器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容