iOS開發------簡單實現圖片多選功能(AssetsLibrary篇)
AssetsLibrary.framework是iOS7.0之前用來擷取手機所有的媒體資源的一個靜態庫,在iOS8.0之後完全可以用Photo.framework來代替,但因為涉及到適配iOS7,所以這個庫用的還是比較多的。
實際上,多選圖片有很多很好用的第三方,但找到一個完全符合自己需求的第三方也不是那麼容易,就算找到,如果不懂,也不是很好修改代碼才對,所以瞭解一下這個庫也是很有必要的,這裡就記錄一下過程中的認識與問題。
如果小夥伴有什麼好玩的庫,還請介紹一下,很希望能和喜歡鑽研技術的你們一起交流。
下面的內容是邊看開發文檔邊研究使用,按自己的理解所寫,如果出現錯誤,也請告知一下Thanks()
本文中的所有代碼:https://github.com/YRunIntoLove/YAImagePickerView
類邏輯
研究一個庫或者架構,總體邏輯一定是要縷清的,下面是個人的理解:
- ALAssetsLibrary 是一個資產庫,所有的資源群組最初都是從這個類的對象中獲得.
- ALAssetsGroup 是一個資源群組,裡麵包含了每個資源群組的基本資料以及包含的所有資源,可以通過設定過濾器屬性來選擇自己想要的資源類型.
- ALAssetsFilter 是一個過濾器,比如需要從資源群組中遍曆出所有的資源,視頻資源還是圖片資源.
- ALAsset 是一個資來源物件,裡面可以取到組的資訊,詳細資料可以通過擷取自身的詳細對象來擷取
- ALAssetRepresentation 是資來源物件的詳細資料,ALAsset的詳細內容都存在這個類中,比如高清圖就存在該對象中。
類庫中的類及其屬性方法
這裡提到的都是代碼中用到的屬性和方法,如果只是為了多圖選擇,那麼以下的方法應該是夠用的,不夠的話可以Command+單擊
進入開發文檔查看即可。
ALAssetsLibrary
資產庫對象:
/***相互關聯類型***/ALAssetsGroupLibrary //從iTunes 來的相簿內容(如本身內建的向日葵照片)ALAssetsGroupAlbum //裝置自身產生或從iTunes同步來的照片,但是不包括照片流跟分享流中的照片。(例如從各個軟體中儲存下來的圖片)ALAssetsGroupEvent //相機介面事件產生的相簿ALAssetsGroupFaces //臉部相簿(具體不清楚)ALAssetsGroupSavedPhotos //"相簿菲林"裡面的照片ALAssetsGroupPhotoStream //照片流ALAssetsGroupAll //除了ALAssetsGroupLibrary上面所的內容
/*遍曆相互關聯類型的資源群組*/- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;
ALAssetsGroup
資源群組對象:
/***屬性的key***/extern NSString *const ALAssetsGroupPropertyName ; //組的標題extern NSString *const ALAssetsGroupPropertyType; //組的類型extern NSString *const ALAssetsGroupPropertyPersistentID ; //組的代表IDextern NSString *const ALAssetsGroupPropertyURL; //該組在本機存放區的位置url
//擷取相關屬性的方法,property的值從上面的屬性key中取即可- (id)valueForProperty:(NSString *)property;//擷取資源群組的預覽圖- (CGImageRef)posterImage;//設定過濾器- (void)setAssetsFilter:(ALAssetsFilter *)filter;//當前組的資源數,如果設定了過濾對象,此數目就是過濾之後儲存資來源物件的個數- (NSInteger)numberOfAssets;
/***遍曆資源的方法***///遍曆所有的資源- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock;//根據類型遍曆- (void)enumerateAssetsWithOptions:(NSEnumerationOptions)option usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock;
//類型typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) { NSEnumerationConcurrent = (1UL << 0), //順序(Default) NSEnumerationReverse = (1UL << 1), //逆序};
ALAssetsFilter
資源過濾器:
//獲得組中所有的照片資源+ (ALAssetsFilter *)allPhotos;//獲得組中所有的視頻資源+ (ALAssetsFilter *)allVideos;//獲得組中所有的資源+ (ALAssetsFilter *)allAssets;
ALAsset
資來源物件:
/***相關屬性的key***/extern NSString *const ALAssetPropertyType; //資來源物件的類型extern NSString *const ALAssetPropertyLocation; //資來源物件的位置描述extern NSString *const ALAssetPropertyDuration; //資來源物件的期間(用於video類型的資來源物件)extern NSString *const ALAssetPropertyOrientation; //資來源物件的方向,如:左轉,右轉..extern NSString *const ALAssetPropertyDate; //資來源物件的建立時間對象extern NSString *const ALAssetPropertyRepresentations; //資來源物件的可用詳細對象數組extern NSString *const ALAssetPropertyURLs; //資來源物件的詳細對象的URLsextern NSString *const ALAssetPropertyAssetURL; //資來源物件的資源標識碼/***相互關聯類型的key***/extern NSString *const ALAssetTypePhoto; //圖片類型extern NSString *const ALAssetTypeVideo; //視頻類型extern NSString *const ALAssetTypeUnknown; //位置類型
//擷取相關屬性的方法,property的值從上面的屬性key中取即可,與ALAssertGroup是一樣的- (id)valueForProperty:(NSString *)property//擷取自身詳細資料對象的一個便利方法- (ALAssetRepresentation *)defaultRepresentation;//擷取一個方形的縮圖,但是不建議用,真是太模糊了- (CGImageRef)thumbnail;//擷取一個按照比例縮放的縮圖,建議用這個,還是比較清晰的(Demo中的屬性用的是這個屬性)- (CGImageRef)aspectRatioThumbnail;
ALAssetRepresentation
資源詳情對象:
//當前對象的方向- (ALAssetOrientation)orientation;//獲得縮放比例- (float)scale;
//獲得一個鋪滿螢幕的高清圖(Demo中預覽的高清圖就是取得這個屬性)- (CGImageRef)fullScreenImage;//如果使用這個屬性,直接用此方法產生UIImage對象UIImage * image = [UIImage imageWithCGImage:fullScreenImage];
//獲得一個重新處理的鋪滿螢幕的高清圖- (CGImageRef)fullResolutionImage;//如果使用這個屬性,需要用下面的方法產生UIImage對象,不然會出現展示方向等錯誤,比片顯示會變成左轉90度的UIImage * image = [UIImage UIImage imageWithCGImage:fullResolutionImage scale:fullResolutionImage.scale orientation:fullResolutionImage.orientation];
Demo部分代碼設定檔_YEnumConfig
//// YEnumConfig.h// YChoosePicturesDemo//// Created by YueWen on 16/4/15.// Copyright ? 2016年 YueWen. All rights reserved.//#ifndef YEnumConfig_h#define YEnumConfig_h@import UIKit;@import AssetsLibrary;#pragma mark - typedef enum : NSUInteger { YChoosePhotoSequenceTypeDefault = 0, //預設是按照選擇的順序 YChoosePhotoSequenceTypeDate //按照圖片在相簿的順序} YChoosePhotoSequenceType;#pragma mark -//照片選擇的Block回調typedef void(^ImagesBlock)(NSArray *);#pragma mark - YPhotoManagertypedef void(^ALAssetGroupBlock)(NSArray * groups);typedef void(^ALAssetPhotoBlock)(NSArray * photos);typedef void(^ALAssetFailBlock)(NSString * error);#pragma mark - YPhotoCollectionViewCelltypedef void(^YPhotoCollectionViewBlock)(void);#endif /* YEnumConfig_h */
圖片請求_YPhotoManager
按照習慣,還是建立了一個YPhotoManager
的單例
@interface YPhotoManager ()@property (nonatomic, strong) ALAssetsLibrary * library; //資產庫@property (nonatomic, copy) ALAssetGroupBlock block; //資源群組進行的回調@property (nonatomic, strong) NSMutableArray * groups; //存放所有照片組的數組對象@property (nonatomic, strong) NSMutableArray * photos; //存放所有照片的數組對象@end
建立單獨的相簿
/** * 建立組名叫做title的相片組 * * @param title 組名 */- (void)createGroupWithTitle:(NSString *)title{ //開始建立 [self.library addAssetsGroupAlbumWithName:title resultBlock:^(ALAssetsGroup *group) { //coding success } failureBlock:^(NSError *error) { //coding error }];}
請求所有的照片組
讀取所有的資源群組對象,其實就是枚舉遍曆,每次獲得一個group對象都會進行一次回調,同步會略微卡頓,所以使用非同步回調Block
#pragma mark - 讀取相簿的所有組/** * 讀取相簿的所有組 * * @param groupBlock 擷取組成功的回調 * @param failBlock 失敗的回調 * @param cameraRollHandle 相機菲林不為nil時候進行的回調 */-(void)readAllPhotoGroups:(ALAssetGroupBlock)groupBlock Fail:(ALAssetFailBlock)failBlock CameraRollHandel:(void (^)(void))cameraRollHandle{ //刪除之前存的所有組 [self.groups removeAllObjects]; __block __weak typeof(self) copy_self = self; //開始遍曆 [self.library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { //如果返回的組存在 if (group) { //對group進行過濾,只要照片 [group setAssetsFilter:[ALAssetsFilter allPhotos]]; //添加資料 [copy_self.groups addObject:group]; //進行順序判斷,目的是將菲林相簿放到第一位 if([self containCameraRoll:group] == true) { //刪除當前位置的數組 [self.groups removeObjectAtIndex:self.groups.count - 1]; [self.groups insertObject:group atIndex:0]; } //回調資料 groupBlock([NSArray arrayWithArray:copy_self.groups]); //進行序列後的回調 if([self containCameraRoll:group] == true) cameraRollHandle(); } } failureBlock:^(NSError *error) { //失敗回調 failBlock(error.localizedDescription); }];}
這裡載入所有的組,所以相機菲林那個組位置不會是第一個,但是又想它在第一行,增加這個判斷來保證存在相機菲林,為其他動作進行準備:
/** * 判斷是否包含相機菲林 * * @param group ALAssetsGroup對象 * * @return true表示包含,false反之 */- (BOOL)containCameraRoll:(ALAssetsGroup *)group{ //如果是相機菲林,放到第一位,這裡只適配英文以及中文 NSString * nameCN = NSLocalizedString([group valueForProperty:ALAssetsGroupPropertyName], @""); NSString * nameEN = NSLocalizedString([group valueForProperty:ALAssetsGroupPropertyName], @""); //對當前組數進行排序 if ([nameCN isEqualToString:@"相機菲林"] || [nameEN isEqualToString:@"Camera Roll"]) { //修改變數 return true; } return false;}
請求組中的所有照片資源
1、要想請求組中的資源,首先要開啟這個組對象
#pragma mark - 開啟相片組-(void)openPhotosGroup:(ALAssetsGroup *)assetsGroup Success:(ALAssetPhotoBlock)successBlock Fail:(ALAssetFailBlock)failBlock{ //刪除所有的照片對象 [self.photos removeAllObjects]; //避免強引用 __block __weak typeof(self) copy_self = self; //擷取當前組的url資料 NSURL * url = [assetsGroup valueForProperty:ALAssetsGroupPropertyURL]; //開啟當前的資源群組對象 [self.library groupForURL:url resultBlock:^(ALAssetsGroup *group) { [copy_self photosInGroups:group Block:successBlock]; } failureBlock:^(NSError *error) { //失敗的回調 failBlock(error.localizedDescription); }];}
2、在開啟的組裡面進行遍曆並回調資料
//擷取所有的照片對象並回調資料- (void)photosInGroups:(ALAssetsGroup *)group Block:(ALAssetPhotoBlock)photoBlock{ //開始讀取 [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { //如果不為空白或者媒體為圖片 if (result != nil && [[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) { //添加資料 [self.photos addObject:result]; //數目達標後統一進行回調 if (index == group.numberOfAssets - 1) { //回調 photoBlock([NSArray arrayWithArray:self.photos]); } } }];}
用到的資料基本都謝了,至於View以及ViewController因為太長,就不在這裡佔地方了,如果有想法,可以下載代碼研究一下。
最後還是要嘮叨一句,不管是UIImageView還是UIButton,顯示縮圖的時候不要忘記設定contentMode屬性,這樣才會出現選擇展示圖中的效果:
//這裡表示如果圖太大,根據高寬中的最大數值進行展示,所以會有一部分不顯示UIViewContentModeScaleAspectFill//他和上面的屬性相反,它是根據高寬終的最小數值進行展示UIViewContentModeScaleAspectFit
//如果是CollectionCell中進行展示的,設定如下Self.contentMode = UIViewContentModeScaleAspectFill;//如果是瀏覽高清圖的時候,設定如下Self.contentMode = UIViewContentModeScaleAspectFit;