AssetsLibrary的詳細使用
AssetsLibrary的組成
AssetsLibrary的組成和iPhone中相冊的實際組成十分的類似,AssetsLibrary庫中的類結構對應相冊中的相冊應用、相冊、相片或視頻,具體的類組成如下:
1.AssetsLibrary:代表iPhone中的資源庫(包含所有的photo、video),可以這么認為,AssetsLibrary就帶包iPhone中的相冊應用。可以通過AssetsLibrary獲取所有的AssetsGroup。同時可以向資源庫中添加相冊;
2.AssetsGroup:映射照片庫(AssetsLibrary)中的一個相冊,同過AssetsGroup可以獲取相冊相應的信息,同時獲取可以通過相冊(AssetsGroup)獲取相冊下的資源,同時也在當前相冊下保存資源;
3.ALAsset:對應相冊中的一張照片或者視頻,ALAsset包含了照片或視頻的詳細信息,可以通過ALAsset獲取縮略圖。另一方面可以使用ALAsset的實例方法保存照片或視頻;
4.ALAssetRepresentation:ALAssetRepresentation 可以理解成是對ALAsset的封裝(但不是其子類),可以更方便地獲取 ALAsset 中的資源信息。通過ALAssetRepresentation可以獲取原圖、全屏圖。每個 ALAsset 都有至少有一個 ALAssetRepresentation 對象,可以通過 defaultRepresentation 獲取。而例如使用系統相機應用拍攝的 RAW + JPEG 照片,則會有兩個 ALAssetRepresentation,一個封裝了照片的 RAW 信息,另一個則封裝了照片的 JPEG 信息。
AssetsLibrary一般用于定制一個圖片選擇器,圖片選擇器相應的可以實現多選、自定義界面。相應的思路應該是:
1.實例化照片庫,列出所有相冊
2.展示相冊中的所有圖片
3.對選圖片或則點擊圖片預覽大圖。
因此AssetsLibrary的講解大體通過上面的過程來詳細的講解。
1. 實例化AssetsLibrary && AssetsLibrary類的詳解
1>實例化AssetsLibrary
實例化很簡單,就普通的alloc init方法,如果你還想精簡那就用new吧
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
2>遍歷照片庫(ALAssetsLibrary)中的所有相冊(AssetsGroup)
self.groups = [NSMutableArray array];
// 遍歷相冊
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) { // 遍歷相冊還未結束
// 設置過濾器
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
if (group.numberOfAssets) {
[self.groups addObject:group];
}
} else { // 遍歷結束(當group為空的時候就意味著結束)
if (self.groups.count) {
// 如果相冊個數不為零,則可以在此處開始遍歷相冊了
//[self enumerateAssets];
} else {
NSLog(@"沒有相冊列表");
}
}
} failureBlock:^(NSError *error) {
NSLog(@"遍歷失敗");
}];
上面的代碼遍歷出了所有的相冊,并把相冊中資源數量不為空的相冊對象(AssetsGroup)的應用存儲到了一個數組中。
需要注意的是:
1.ALAssetsLibrary需要相冊為空,即相冊中沒有任何資源。如果你不想把為空的相冊保存到數組中,可以通過AssetsGroup的numberOfAssets屬性判斷相冊是否為空,為空則不保存。
2.ALAssetsGroup有一個過濾器的實例方法:setAssetsFilter,通過此方法傳入的枚舉值,可以設置只獲取相冊中的photo或者video。同時設置后,或更新AssetsGroup對應的numberOfAssets。
3.在AssetsLibrary框架之下,所有的遍歷操作都是異步(Asynchronous)執行的。因為用戶的資源庫、相冊可能很大,這種條件之下,異步遍歷不至于堵塞主線程。另外,當遍歷對應的block中的輸出結果參數為空(nil),則意味遍歷結束。當然,通過給定的stop參數,你也可以在滿足某種條件下手動的停止遍歷。
2. 遍歷相冊(AssetsGroup)中的所有相片或視頻(Asset)
AssetsGroup提供了三種遍歷方式:
1.- (void)enumerateAssetsUsingBlock:
2.- (void)enumerateAssetsWithOptions:usingBlock:
3.- (void)enumerateAssetsAtIndexes:options:usingBlock:
遍歷相冊:
- (void)enumerateAssets {
NSMutableArray * assets = [NSMutableArray new];
for (ALAssetsGroup * group in self.groups) {
/*
// 遍歷所有的相片
[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) { // 遍歷未結束
[assets addObject:result];
} else { // result 為nil,遍歷結束
}
}];
*/
// 遍歷指定的相片
NSInteger fromIndex = 0; // 重指定的index開始遍歷
NSInteger toIndex =5; // 指定最后一張遍歷的index
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:toIndex] options:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (index > toIndex) { // 已經遍歷到指定的最后一張照片
*stop = YES; // 停止遍歷
} else {
if (result) {
// 存儲相片
[assets addObject:result];
} else { // 遍歷結束
// 展示圖片
//[self showPhotoWith:result];
}
}
}];
}
}
遍歷相冊(ALAssetsGroup)中的照片(ALAsset)和遍歷資源庫(ALAssetsLibrary)中的相冊過程十分的類似,不過遍歷相冊中的照片更加的靈活,可以通過enumerateAssetsUsingBlock: 遍歷所有的照片,同時可以通過enumerateAssetsAtIndexes:options:usingBlock:方法遍歷指定index的照片。這正如上面代碼中段注釋類是遍歷所有的照片,段注釋下面遍歷指定index一樣。
ALAssetsGroup的詳解
ALAssetsGroup代表的是一個相冊,這個概念在上面已經說得夠詳細了,就不介紹了。在這里主要說說ALAssetsGroup類中的方法與屬性的含義和用法。
- (void)showALAssetsGroupInfo:(ALAssetsGroup *)assetsGroup {
// 是否可編輯,即相冊是否可以通過代碼添加相片
BOOL editable = assetsGroup.editable;
// 添加一個ALAsset到當前相冊,前提editable = YES,
[assetsGroup addAsset:nil];
/**
+ (ALAssetsFilter *)allPhotos; // 獲取Photo
+ (ALAssetsFilter *)allVideos; // 獲取Video
+ (ALAssetsFilter *)allAssets; // 獲取Photo還有video
*/
// 設置過濾器
[assetsGroup setAssetsFilter:[ALAssetsFilter allPhotos]];
// 當前過濾器下的ALAsset數量
NSInteger number = assetsGroup.numberOfAssets;
/**
NSString *const ALAssetsGroupPropertyName; // Group的名稱
NSString *const ALAssetsGroupPropertyType; // Group類型(ALAssetsGroupType)
NSString *const ALAssetsGroupPropertyPersistentID;
NSString *const ALAssetsGroupPropertyURL; // 唯一表示這個Group的URL,可以通過URL在資源庫中獲取對應的Group,用于唯一標識這個group
*/
// 通過Property獲取ALAssetsGroup對應的信息
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyName]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyType]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyPersistentID]);
NSLog(@"%@", [assetsGroup valueForProperty:ALAssetsGroupPropertyURL]);
// 獲取相冊封面
[assetsGroup posterImage];
}
3 獲取相片(ALAsset)中的詳細內容
在這里需要詳細的說明一下,所有的ALAsset所對應的照片信息存在與ALAsset中的ALAssetRepresentation對象中,通過ALAssetRepresentation可以獲取這個ALAsset對應的原圖、修改過后的圖。而通過ALAsset可以獲取ALAsset對應的縮略圖。另外需要說明的是,一個ALAsset可能對應多個ALAssetRepresentation,原因在與相冊取圖的時候可能抓取多種不同格式的圖片,例如:RAW和JPEG格式的圖片,此時ALAsset就存在兩個ALAssetRepresentation,此時就可以通過不同的ALAssetRepresentation獲取不同格式的原圖、修改后的全屏圖。
獲取原圖、全屏圖:
- (void)showPhotoWith:(ALAsset *)asset {
// 獲取ALAsset對應的ALAssetRepresentation
ALAssetRepresentation * representation = [asset defaultRepresentation];
NSLog(@"%@", representation.url); // 圖片URL
NSLog(@"%@", NSStringFromCGSize(representation.dimensions)); // 圖片尺寸
NSLog(@"%lld", representation.size); // 數據字節
NSLog(@"%@", representation.UTI); // Uniform Type Identifier : 統一類型標識符(表示圖片或視頻的類型)
NSLog(@"%@", representation.filename); // 在相冊中的文件名
NSLog(@"%@", representation.metadata); // 元數據(一些設備相關的信息,比如使用的相機)
NSLog(@"%lf", representation.scale); // 縮放比例
NSLog(@"%ld", representation.orientation); // 方向
/**
fullScreenImage : 返回當前設備尺寸大小的圖片,編輯后的圖片
fullResolutionImage : 原圖,沒有編輯的圖片
*/
// 獲取原圖
UIImage * image = [UIImage imageWithCGImage:[representation fullScreenImage] scale:1.0 orientation:UIImageOrientationDownMirrored];
self.imageView.image = image;
}
fullResolutionImage 是圖片的原圖,通過 fullResolutionImage 獲取的圖片沒有任何處理,包括通過系統相冊中“編輯”功能處理后的信息也沒有被包含其中,因此需要展示“編輯”功能處理后的信息,使用 fullResolutionImage 就比較不方便,另外 fullResolutionImage 的拉取也會比較慢,在多張 fullResolutionImage 中切換時能明顯感覺到圖片的加載過程。因此這里建議獲取圖片的 fullScreenImage,它是圖片的全屏圖版本,這個版本包含了通過系統相冊中“編輯”功能處理后的信息,同時也是一張縮略圖,但圖片的失真很少,缺點是圖片的尺寸是一個適應屏幕大小的版本,因此展示圖片時需要作出額外處理,但考慮到加載速度非常快的原因(在多張圖片之間切換感受不到圖片加載耗時),仍建議使用 fullScreenImage。
ALAsset詳解:
- (void)showALAssetInfoWith:(ALAsset *)asset {
/**
NSString *const ALAssetPropertyType;
NSString *const ALAssetPropertyLocation;
NSString *const ALAssetPropertyDuration;
NSString *const ALAssetPropertyOrientation;
NSString *const ALAssetPropertyDate;
NSString *const ALAssetPropertyRepresentations;
NSString *const ALAssetPropertyURLs;
NSString *const ALAssetPropertyAssetURL;
*/
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyType]); // 這個type表示這個是photo還是video
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyLocation]); // 拍攝的地點
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyDuration]); // 視頻的時長
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyOrientation]); // 照片的方向
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyDate]); // 照片的拍攝時間
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyRepresentations]);
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyURLs]); //
NSLog(@"%@", [asset valueForProperty:ALAssetPropertyAssetURL]);
[asset thumbnail]; // 返回縮略圖
[asset aspectRatioThumbnail]; // 等比例縮略圖
[asset representationForUTI:@"public.jpeg"]; // 通過統一類型標識獲取ALAssetRepresentation
// ALAsset 還具有更改ALAsset中的元數據能力
// ALAsset 的實例方法能保存Photo、video到相冊
}
4. AssetsLibrary 的坑點
- AssetsLibrary 實例需要強引用
實例一個 AssetsLibrary 后,如上面所示,我們可以通過一系列枚舉方法獲取到需要的相冊和資源,并把其儲存到數組中,方便用于展示。但是,當我們把這些獲取到的相冊和資源儲存到數組時,實際上只是在數組中儲存了這些相冊和資源在 AssetsLibrary 中的引用(指針),因而無論把相冊和資源儲存數組后如何利用這些數據,都首先需要確保 AssetsLibrary 沒有被 ARC 釋放,否則把數據從數組中取出來時,會發現對應的引用數據已經丟失(參見下圖)。這一點較為容易被忽略,因此建議在使用 AssetsLibrary 的 viewController 中,把 AssetsLibrary 作為一個強持有的 property 或私有變量,避免在枚舉出 AssetsLibrary 中所需要的數據后,AssetsLibrary 就被 ARC 釋放了。 - AssetsLibrary 遵循寫入優先原則
寫入優先也就是說,在利用 AssetsLibrary 讀取資源的過程中,有任何其它的進程(不一定是同一個 App)在保存資源時,就會收到 ALAssetsLibraryChangedNotification,讓用戶自行中斷讀取操作。最常見的就是讀取 fullResolutionImage 時,用進程在寫入,由于讀取 fullResolutionImage 耗時較長,很容易就會 exception。 - 開啟 Photo Stream 容易導致 exception
本質上,這跟上面的 AssetsLibrary 遵循寫入優先原則是同一個問題。如果用戶開啟了共享照片流(Photo Stream),共享照片流會以 mstreamd 的方式“偷偷”執行,當有人把相片寫入 Camera Roll 時,它就會自動保存到 Photo Stream Album 中,如果用戶剛好在讀取,那就跟上面說的一樣產生 exception 了。由于共享照片流是用戶決定是否要開啟的,所以開發者無法改變,但是可以通過下面的接口在需要保護的時刻關閉監聽共享照片流產生的頻繁通知信息。
[ALAssetsLibrary disableSharedPhotoStreamsSupport];