自定義相冊和選取

一.引言

最近項目中要用到相冊和選取截圖,但是系統(tǒng)的不太符合設計要求贮尉,所以就自己實現(xiàn)了一個.
  首先說說做前普及的知識.在iOS中,關于相冊的系統(tǒng) API有兩個,準確來說其實是一個.分別為AssetsLibrary和Photos莹汤,AssetsLibrary框架是iOS7之前的關于相冊的系API,而在iOS8的時候由于AssetsLibrary框架的一些問題.所以蘋果推出了新的API:Photos颠印,知道iOS9的時候直接已經(jīng)廢棄了AssetsLibrary框架纲岭,但是我們在項目還要兼容iOS7,所以要對這兩個分別來配置下.

二. AssetsLibrary框架

AssetsLibrary框架是 iOS7之前的相冊系統(tǒng)框架,iOS9已被廢棄,相似于一個數(shù)據(jù)集合,而想要獲得某類型的照片或者照片組就得按照相應的規(guī)則去遍歷數(shù)據(jù)集合,來獲得想要的.這樣的性能是不是太好.接下來看看主要組成該框架的類.

  • AssetsLibrary:包括相冊資源集合,以及篩選,存入,權限等api類.此類大概分三大塊,一塊是保存圖片到數(shù)據(jù)集合,一塊是讀取數(shù)據(jù)集合中的圖片,最后一塊是用戶相關權限:

保存類api 是以write開頭的一些 api

- (void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef 
                         orientation:(ALAssetOrientation)orientation
                     completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock; 

保存一個圖片到相冊:
imageRef:保存的圖片, CGImageRef類型,可由[UIImage CGImage]獲得
orientation:保存圖片時,圖片的方向,可由[UIImage imageOrientation]或得,枚舉:
    ALAssetOrientationUp 上
    ALAssetOrientationDown 下
    ALAssetOrientationLeft 左
    ALAssetOrientationRight 右
    ALAssetOrientationUpMirrored 水平上翻轉
    ALAssetOrientationDownMirrored 水平下翻轉
    ALAssetOrientationLeftMirrored 豎直左翻轉
    ALAssetOrientationRightMirrored 豎直右翻轉
completionBlock:回調(diào) block, 定義為:typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error)
成功回調(diào)得到 iamge 的 url.這里的 url 是本地的,可以用它來檢索 image,error: 回調(diào)到錯誤信息.
其他保存 aip 類似.

讀取類 api,這說下常用的遍歷ALAssetsLibrary集合的.

- (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types 
                      usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock 
                    failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;

遍歷資源集合獲得篩選到的相冊:
types:刪選條件,枚舉:
   ALAssetsGroupLibrary 所有的相冊(寶庫本地和 iTunes 的)
   ALAssetsGroupAlbum  同步得到iTunes的和創(chuàng)建的(不包括共享的)
   ALAssetsGroupEvent  同步到 iTunes 的(包括相機導入的)
   ALAssetsGroupFaces  同步 iTunes 的 
   ALAssetsGroupSavedPhotos 所有相冊(包括相機導入的)
   ALAssetsGroupPhotoStream itunes 同步的(包括共享的)
   ALAssetsGroupAll 所有可見的相冊
enumerationBlock:成功回調(diào) block, 定義:typedef void (^ALAssetsLibraryGroupsEnumerationResultsBlock)(ALAssetsGroup *group, BOOL *stop) 
獲得 group: 每個圖片或視屏組成的相冊,stop: 停止遍歷的標示
failureBlock:失敗的回調(diào):定義:typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error)
error: 錯誤信息.

權限設置類:

+ (ALAuthorizationStatus)authorizationStatus;
類方法,返回值一個ALAuthorizationStatus的枚舉類型,來表明當時的用戶權限狀況.
    ALAuthorizationStatusNotDetermined 用戶還未做出選擇 
    ALAuthorizationStatusRestricted 用戶權限設置(如家長模式)
    ALAuthorizationStatusDenied 明確選擇過不允許獲得權限
    ALAuthorizationStatusAuthorized 同意獲取
  • ALAssetsGroup:資源中某個相冊資源,可以獲取相冊中具體照片或者視屏,添加或者獲取詳細相冊信息,列出幾個常用主要的方法:
(1):獲取相冊數(shù)量:
- (NSInteger)numberOfAssets;
獲取數(shù)量是和你的刪選條件有關filter.
(2):刪選條件:
- (void)setAssetsFilter:(ALAssetsFilter *)filter;
給定一個刪選條件.
filter:刪選的一個條件,ALAssetsFilter類型的類方法
+ (ALAssetsFilter *)allPhotos; 所有的圖片
+ (ALAssetsFilter *)allVideos; 所有的視屏
+ (ALAssetsFilter *)allAssets; 所有的資源(包括圖片和視頻)

(3):相冊封面照片:
- (CGImageRef)posterImage;
獲得相冊的封面照片,該返回的圖片方向是正確的.
(4):返回相冊的一些相關屬性:
- (id)valueForProperty:(NSString *)property;
property:相冊相關的一些屬性,定義了常量:
extern NSString *const ALAssetsGroupPropertyName 相冊的名稱 
extern NSString *const ALAssetsGroupPropertyType 相冊的類型
extern NSString *const ALAssetsGroupPropertyPersistentID 相冊的標示 id
extern NSString *const ALAssetsGroupPropertyURL 相冊的 url
(5):獲取相冊中的資源:
- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock;
enumerationBlock:遍歷資源回調(diào)的 block, 定義為:typedef void (^ALAssetsGroupEnumerationResultsBlock)(ALAsset *result, NSUInteger index, BOOL *stop);
result:一個圖片或者資源的類,包含了資源的一些信息.
index:遍歷相冊時result 對應的下標位置.
stop:對遍歷的操作控制.如果有特殊條件可以在某個位置停止獲取資源.
  • ALAsset:相冊中每個對象,包含了相片或者視頻的一些信息.可以寫入或者獲取到一些資源.
(1):返回一個ALAssetRepresentation對象,一個ALAsset對像默認有一個ALAssetRepresentation對象,用他來獲取更加詳細的每個具體資源的信息,如:原圖,大小,字節(jié)等.
- (ALAssetRepresentation *)defaultRepresentation;
(2):獲得縮略圖:
- (CGImageRef)thumbnail;
該方法獲得相對質量不高的縮略圖,他是對原圖進行從最中間進行正方形的裁剪.改圖不是按比例縮小.圖片的方向的正確的.
(3):獲得縮略圖:
- (CGImageRef)aspectRatioThumbnail;
和上面的方法得到的都是縮略圖,但是不同的是他是按圖片原先的比例按比例縮小的
(4).保存圖片到相冊:
- (void)writeModifiedImageDataToSavedPhotosAlbum:(NSData *)imageData metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock;
保存圖片到相冊:
imageData:要保存的圖片流,
metadata:保存圖片的元數(shù)據(jù),一般為 nil,如果和前面的 imageData 沖突的話,會覆蓋掉前面的信息,
completionBlock:保存完的 block 回調(diào),定義為:typedef void (^ALAssetsLibraryWriteImageCompletionBlock)(NSURL *assetURL, NSError *error),
assetUrl: 保存成功后映射該相片的 url
error: 保存失敗信息.
  • ALAssetRepresentation:對 ALAsset 的封裝,包含了每個相冊的一些詳細信息,可以用defaultRepresentation獲得默認的那個該對象.這里要注意的是,如果是相冊的話,默認是一個該對象,而如果是相機拍照的情況下.默認是有兩個該對象的,一個包含了RAW 的信息,一個包含了JPEG的信息.

三.Photos 框架

Photos 是 iOS8時蘋果新推出的一個關于系統(tǒng)相冊的新框架.改框架應該跟AssetsLibrary的處理不一樣,他不是去根據(jù)條件遍歷數(shù)據(jù)資源,而是根據(jù)條件直接獲得指定的資源.所以相比而言,更加高效和完整.下來看看主要組成該框架的類.

  • PHAssetCollection:PHCollection的子類,相冊分類,一系列的相冊.如:最近刪除.精選等.常用 api:
(1).該相冊的類型
assetCollectionType;
PHAssetCollectionType的枚舉值:
    PHAssetCollectionTypeAlbum      相冊
    PHAssetCollectionTypeSmartAlbum 智能相冊
    PHAssetCollectionTypeMoment 時刻
(2).該相冊的子類型
assetCollectionSubtype;
PHAssetCollectionSubtype枚舉類型:
常規(guī)的子類型
    PHAssetCollectionSubtypeAlbumRegular    常規(guī)的     
    PHAssetCollectionSubtypeAlbumSyncedEvent    使用 iTunes 同步操作過來的相冊 
    PHAssetCollectionSubtypeAlbumSyncedFaces    使用 iTuens同步操作過來的人物相冊
    PHAssetCollectionSubtypeAlbumSyncedAlbum     使用iTunes 同步的所有相冊
    PHAssetCollectionSubtypeAlbumImported        從外界導入的相冊
    
經(jīng)分享的子類型
    PHAssetCollectionSubtypeAlbumMyPhotoStream   從相冊分享得到
    PHAssetCollectionSubtypeAlbumCloudShared     從 cloud 分享得到
智能相冊子類型
    PHAssetCollectionSubtypeSmartAlbumGeneric    通用的
    PHAssetCollectionSubtypeSmartAlbumPanoramas  全景
    PHAssetCollectionSubtypeSmartAlbumVideos     視屏
    PHAssetCollectionSubtypeSmartAlbumFavorites  收藏
    PHAssetCollectionSubtypeSmartAlbumTimelapses 延時視屏,也會在PHAssetCollectionSubtypeSmartAlbumVideos在出現(xiàn)
    PHAssetCollectionSubtypeSmartAlbumAllHidden  隱藏的
    PHAssetCollectionSubtypeSmartAlbumRecentlyAdded 最近添加
    PHAssetCollectionSubtypeSmartAlbumBursts    連拍 
    PHAssetCollectionSubtypeSmartAlbumSlomoVideos Slomo是slow motion的縮寫,高速攝影慢動作解析
    PHAssetCollectionSubtypeSmartAlbumUserLibrary 用戶所有的資源
    PHAssetCollectionSubtypeSmartAlbumSelfPortraits 所有前置攝像頭拍的照片和視屏
    PHAssetCollectionSubtypeSmartAlbumScreenshots 所有的截屏圖
不關心子類型時的全部資源
    PHAssetCollectionSubtypeAny = NSIntegerMax
(3).獲得相冊的集合資源
+ (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
返回一個相冊集合資源,集合里面是PHAssetCollection類型的對象.
type:相冊類型, PHAssetCollectionType類型的枚舉,
subtype:子類型, PHAssetCollectionSubtype的枚舉,
PHFetchOptions: PHFetchOptions的一個實例,可以為空,只要是為了對獲得資源做一些配置和排序等.可以為 nil.
  • PHFetchOptions:對使用 PHAsset, PHCollection, PHAssetCollection, 和 PHCollectionLis 的方法時出入的參數(shù),主要對獲取到資源做一些配置和排序等,一般為 nil, 默認使用系統(tǒng)的.
(1).排序
sortDescriptors;
(2).是否顯示隱藏的相冊,默認不顯示
includeHiddenAssets
(3).獲取到相冊的類型
includeAssetSourceTypes;
PHAssetSourceType類型的枚舉,默認PHAssetSourceTypeNone
    PHAssetSourceTypeNone   都沒有,就獲得到就是常規(guī)的         
    PHAssetSourceTypeUserLibrary     用戶所有的
    PHAssetSourceTypeCloudShared     分享的    
    PHAssetSourceTypeiTunesSynced    iTunes 同步的
  • PHFetchResult:相冊資源,包括相冊中圖片的數(shù)量和獲取.看些常用 api.
(1).相冊中圖片的數(shù)量
count
(2).遍歷得到相冊資源中每個相冊組的信息.
- (void)enumerateObjectsUsingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
block為成功回調(diào),定義為:void (^)(ObjectType obj, NSUInteger idx, BOOL *stop);
obj:改參數(shù)由遍歷他的集合決定,如果是PHAssetCollection 的 api 獲得.那么他就是PHAssetCollection的對象,包括每組相冊的一些信息;
如果是PHAsset api 獲得,那他就是 PHAsset 對象,包含具體的每張圖片的信息.
idx:遍歷時每組相冊對應在集合中的下標.
stop:用于在某時刻停止遍歷資源.
  • PHAsset:包含具體的每個照片的資源信息. 看看些常用的 api.
(1).資源的原信息.
mediaType:PHAssetMediaType類型的枚舉值:
    PHAssetMediaTypeUnknown 不知類型
    PHAssetMediaTypeImage   圖片
    PHAssetMediaTypeVideo   視屏
    PHAssetMediaTypeAudio   音頻
(2).資源的子類型.
mediaSubtypes:PHAssetMediaSubtype類型的枚舉值:
    PHAssetMediaSubtypeNone               沒有任何子類型
    相片子類型
    PHAssetMediaSubtypePhotoPanorama      全景圖
    PHAssetMediaSubtypePhotoHDR           濾鏡圖
    PHAssetMediaSubtypePhotoScreenshot 截屏圖
    PHAssetMediaSubtypePhotoLive 1.5s 的 photoLive   
    視屏子類型
    PHAssetMediaSubtypeVideoStreamed      流體
    PHAssetMediaSubtypeVideoHighFrameRate 高幀視屏
    PHAssetMediaSubtypeVideoTimelapse   延時拍攝視頻
(3).獲得 PHAsset 的集合.
+ (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
  • PHImageManager:管理 PHAsset 的一個類,相當于對一個具體資源更好地管理和篩選.看寫常用的 api.
(1).獲得該實例.
+ (PHImageManager *)defaultManager;
(2).經(jīng)刪選和限制條件獲得具體的資源UIImage.
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset 
                              targetSize:(CGSize)targetSize 
                             contentMode:(PHImageContentMode)contentMode
                                 options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
返回值: PHImageRequestID,是個常量,定義為:static const PHImageRequestID PHInvalidImageRequestID = 0;
asset:想要獲得信息的PHAsset的對象,
targetSize:獲得圖片的尺寸大小,這里的大小是pixel,所以換算乘以[UIScreen mainScreen].scale.獲得自己想要的尺寸.
如果想要原圖的尺寸,直接傳入PHImageManagerMaximumSize.很大很大的尺寸,系統(tǒng)會默認返回原圖的尺寸,要注意的是傳入PHImageManagerMaximumSize時,則 contentMode 無論傳入什么值都會被視為PHImageContentModeDefault.
contentMode:想要圖片的裁剪方式, PHImageContentMode的枚舉:
    PHImageContentModeAspectFit  適合的
    PHImageContentModeAspectFill 鋪滿的
    PHImageContentModeDefault = PHImageContentModeAspectFit
options: PHImageRequestOptions的實例,包括控制圖片版本,質量,裁剪參數(shù)等的一個類.
resultHandler:成功回調(diào)block,
result:獲取到的具體圖片,
info:關于圖片的一些信息,如是否來自 cloud, 是否是原圖等.
  • PHCachingImageManager: PHImageManager的子類,讀獲取圖片的過程做緩存和清理的一個類.看看一些常用的 api.
(1).緩存操作.
- (void)startCachingImagesForAssets:(NSArray<PHAsset *> *)assets
                         targetSize:(CGSize)targetSize 
                        contentMode:(PHImageContentMode)contentMode
                            options:(nullable PHImageRequestOptions *)options;
assets:要緩存獲取 PHAsset 類型對象的集合.
targetSize:獲取時的尺寸.
contentMode:裁剪方法,
options:傳入的控制參數(shù)類.
(2).取消緩存操作.
- (void)stopCachingImagesForAssets:(NSArray<PHAsset *> *)assets targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options;
assets:要緩存獲取 PHAsset 類型對象的集合.
targetSize:獲取時的尺寸.
contentMode:裁剪方法,
options:傳入的控制參數(shù)類.
  • PHImageRequestOptions:控制加載圖片參數(shù)的一個類.看些常用的 api.
(1).控制圖片質量和獲取速度的 api
deliveryMode.
PHImageRequestOptionsDeliveryMode類型的枚舉,只有synchronous屬性設置為 YES,即異步獲取有限
    PHImageRequestOptionsDeliveryModeOpportunistic  圖片質量和獲取速度均衡
    PHImageRequestOptionsDeliveryModeHighQualityFormat 獲取高質量圖片,不保證獲取速度
    PHImageRequestOptionsDeliveryModeFastFormat 快速獲得,不保證質量 
(2).裁剪的方式
resizeMode.
PHImageRequestOptionsResizeMode類型的枚舉:
PHImageRequestOptionsResizeModeNone 不設置    PHImageRequestOptionsResizeModeFast  返回的圖像可能和目標大小不一樣并且質量較低,但效率高.
    PHImageRequestOptionsResizeModeExact 返回圖像必須和目標大小相匹配,并且圖像質量也為高質量圖像
  • PHPhotoLibrary:相冊權限管理,監(jiān)聽相冊增刪等.看看常用 api
(1).獲得實例
+ (PHPhotoLibrary *)sharedPhotoLibrary;
(2).獲得當前用戶授權情況
+ (PHAuthorizationStatus)authorizationStatus;
返回一個PHAuthorizationStatus類型的枚舉:
    PHAuthorizationStatusNotDetermined 用戶還未選擇           
    PHAuthorizationStatusRestricted 家長模式                                       不允許
    PHAuthorizationStatusDenied 不同意訪問          
    PHAuthorizationStatusAuthorized 同意訪問
(3).在第一次獲取時,系統(tǒng)提示選擇完后的回調(diào)方法
+ (void)requestAuthorization:(void(^)(PHAuthorizationStatus status))handler;
handler:該 block 就是用戶授權選擇完后的回調(diào),可獲得用戶選擇的結果

四.項目

主要分為兩個大模塊.一起是獲取相冊中的圖片,另一個為對獲取的圖片進行想要的尺寸裁剪.

  • 獲取相冊中的圖片,單獨創(chuàng)建一個類 WFPhotoAlbum,作為獲取系統(tǒng)相冊資源的 manager,在內(nèi)部定義三個數(shù)組,在外部聲明一個公開的方法用來外部調(diào)用:
//相片原圖
@property (nonatomic, strong) NSMutableArray *fullPhotos;
//縮略圖
@property (nonatomic, strong) NSMutableArray *thumbnails;
//相冊列表資源
@property (nonatomic, strong) NSMutableArray *albums

/**
 *  獲取圖片方法
 *
 *  @param success 成功回調(diào)(分組列表,原圖,縮略圖)
 *  @param failure 失敗回調(diào)(error)
 */
- (void)getPhotosSuccess:(void (^)(NSMutableArray *groupPhotos,
                                   NSMutableArray *fullPhotos,
                                   NSMutableArray *thumbnails))success 
                 failure:(void (^)(NSError *error))failure;

在方法的實現(xiàn)內(nèi)部,將耗時的操作放在子線程,比如讀取相冊資源等,吧主要的額回調(diào)提示等放在主線程.我們分 iOS8以下和 iOS8以上,在 iOS8以下,用的AssetsLibrary框架,將相片組的名稱和數(shù)量記錄下來:

[_albums addObject:[NSString stringWithFormat:@"%@(%li)張",[group valueForProperty:ALAssetsGroupPropertyName],group.numberOfAssets]];

AssetsLibrary框架可以直接提供縮略圖,直接保存下縮略圖:

[_thumbnails addObject:[UIImage imageWithCGImage:CGImage]];

保存原圖,這個地方保存的時候要壓縮下,不然相片一多就會內(nèi)存警告,會 crash:

[_fullPhotos addObject:UIImageJPEGRepresentation([UIImage imageWithCGImage:imageReference], 0.4)];

下面是iOS8以上的,用 Photos 框架的將相冊的名稱記錄下來:

[_albums addObject:obj.localizedTitle];

應為 Photos 采取的機制不一樣,他是由條件直接獲取到對應的資源,所以只要遍歷得到資源,就會占用內(nèi)存.在圖片稍微一多就會 crash, 所以我們在這采用保存 PHAsset 對象的辦法來解決這個crash, 這也是蘋果官方所采用的解決辦法.

PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:nil];
               
[assetsFetchResults enumerateObjectsUsingBlock:^(PHAsset  *_Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                   [_thumbnails addObject:obj];
                   [_fullPhotos addObject:obj];
    }];

這里要注意兩點:

  • AssetsLibrary對象應該是個全局變量,而不是局部變量,如果在他的生命周期外,我們所得到的資源是空的,這和他遍歷獲取資源庫的機制有關.
  • 在AssetsLibrary框架下,如果用戶第一次選擇了權限,他會根據(jù)用戶的權限,會自動再去判斷獲取請求資源或者不請求,這個再次請求和用戶點擊授權的操作是系統(tǒng)自動處理的.保證用戶受了權限,在還能去請求一次資源,正常獲得資源,而 Photos 框架下是要開發(fā)者自己去管理,所以就有個那個用戶授權完的一個回調(diào),開發(fā)者需自行判斷再去是否請求一次資源.

我們在得到回調(diào)的資源時,去對遍歷操作做一次緩存.

if ([self.thumbnails.firstObject isKindOfClass:[PHAsset class]]) {
            // 在資源的集合
            _imageManager = [[PHCachingImageManager alloc] init];
            //緩存操作
            [_imageManager startCachingImagesForAssets:self.thumbnails
                                            targetSize:PHImageManagerMaximumSize
                                           contentMode:PHImageContentModeAspectFill
                                               options:nil];
        }

然后再在賦值的時候拿出記錄的 PHAsset 對象,給相應的控件賦值.此處獲取到的縮略圖只要按照原圖比例得到的小圖,要是直接使用,會產(chǎn)生變形,所以我們還要講此圖進行裁剪,按寬高,或得圖片正中間的一個正方向的圖.

//賦值
if ([obj isKindOfClass:[PHAsset class]]){

        PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
        //異步
        requestOptions.synchronous = YES;
        //速度和質量均衡//synchronous ture 時有效
        requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
        //盡快提供要求左右的尺寸圖
        requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
        // 遍歷資源的集合,獲取其中的圖片
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [_imageManager requestImageForAsset:obj
                                    targetSize:CGSizeMake(125 * [UIScreen mainScreen].scale,
                                                          125 * [UIScreen mainScreen].scale)
                                   contentMode:PHImageContentModeDefault
                                       options:requestOptions
                                 resultHandler:^(UIImage *result, NSDictionary *info) {
                                     dispatch_async(dispatch_get_main_queue(), ^{
                                         cell.imageView.image = [self wf_thumbnailsCutfullPhoto:result];
                                     });
                                     
                                 }];
        });
    }
    
//裁剪圖片,此處裁剪為125*125大的圖,即為我們的縮略圖
- (UIImage *)wf_thumbnailsCutfullPhoto:(UIImage*)fullPhoto
{
    CGSize newSize;
    CGImageRef imageRef = nil;
    if ((fullPhoto.size.width / fullPhoto.size.height) < 1) {
        newSize.width = fullPhoto.size.width;
        newSize.height = fullPhoto.size.width * 1;
        imageRef = CGImageCreateWithImageInRect([fullPhoto CGImage], CGRectMake(0, fabs(fullPhoto.size.height - newSize.height) / 2, newSize.width, newSize.height));
        
    } else {
        newSize.height = fullPhoto.size.height;
        newSize.width = fullPhoto.size.height * 1;
        imageRef = CGImageCreateWithImageInRect([fullPhoto CGImage], CGRectMake(fabs(fullPhoto.size.width - newSize.width) / 2, 0, newSize.width, newSize.height));
        
    }
    return [UIImage imageWithCGImage:imageRef];
}
  • 裁剪獲得想要的相片尺寸.我是用 CAShapeLayer 做了個裁剪框,這樣的話,就不會影響到下面圖片的縮放,平移的交互,且將 layer 一定要加載在最上層.而此處的要被操作的圖片則是圖片原圖,且該處承載圖片的容器 UIImageView 也要用圖片的比例去動態(tài)設置.
      平移縮放給視圖加的手勢來完成的,并且限制了一些操作,保證平移時圖片一直在裁剪框中,縮放時圖片不能小于裁剪框.這里就不寫代碼了,有點多,稍后附上 demo 地址,有興趣的小伙伴可以下載看看.效果圖:


    裁剪框效果圖.png

      最后就是選擇后確認裁剪,其實很簡單,就是將在整個大 view 的圖上的裁剪框內(nèi)的區(qū)域生成圖片,則這張圖就是咋們需要用到的圖片.然后經(jīng)過回調(diào)到咋們需要的地方去.

- (UIImage *)private_captureImageFromView:(UIView *)view{
    CGRect screenRect = CGRectMake((self.view.frame.size.width - rectWidth) / 2,
                                   (self.view.frame.size.height - rectHeight) / 2,
                                   rectWidth,
                                   rectHeight);
    UIGraphicsBeginImageContextWithOptions(screenRect.size, NO, [UIScreen mainScreen].scale);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context == NULL){
        return nil;
    }
    //copy 一份圖形上下位文,用來操作
    CGContextSaveGState(context);
    //將當前圖行位文進行矩陣變換
    CGContextTranslateCTM(context, -screenRect.origin.x, -screenRect.origin.y);
    
    if( [view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]){
        //捕捉當前快照
        [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    }else{
        //layer 層渲染
        [view.layer renderInContext:context];
    }
    //圖形位文退出棧
    CGContextRestoreGState(context);
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

最后這里附上demo 的地址,有興趣的小伙伴可以下載看看.! Demo.

修注:

在后面該項目中此模塊時,發(fā)現(xiàn)存在內(nèi)存問題.當圖片很多,上千上萬張,或者不停地一直滑動,就會出現(xiàn) ReceiveMemoryWarning,直至程序 crash.

出現(xiàn)這個問題后,在網(wǎng)上調(diào)研了一些資料,終于定位到問題的所在.所出現(xiàn)的內(nèi)存問題有兩個:

  • 獲取完圖片后管理在圖片數(shù)量龐大時,稍顯乏力.

解決方法:重新做了圖片獲取后的管理.引入了兩個類. WFAlumbModel 和 WFCacheModel ,用于管理和交接在圖片和控制器之間的工作.獲取的方法改動為:

/**
 獲取相冊的方法

 @param success 成功獲取到模型 model
 */
- (void)getCameraSuccess:(void (^)(WFCacheModel *))success;

此方法內(nèi)部開辟子線程,先調(diào)用 WFCacheModel 的實例方法,將 PHFetchResult/ALAssetsLibrary 對象保存在 model 的 result 屬性中.

- (WFCacheModel *)modelWithResult:(id)result{
    WFCacheModel *model = [[WFCacheModel alloc] init];
    model.result = result;
    return model;
}

然后重寫 result 的 set 方法,在 set 方法中調(diào)用 WFPhotoAlbum 的方法.

- (void)getAssetsFromFetchResult:(id)result
                         completion:(void (^)(NSArray<WFAlumbModel *> *))completion;

在此方法中方法,創(chuàng)建 WFAlumbModel 對象,將 PHAsset/ALAsset 對象保存在 WFAlumbModel 的 asset 屬性中.然后添加在數(shù)組中以 block 的方式返回賦值給 WFCacheModel 的 models 屬性,然后在獲取相冊的方法中. success 成功回調(diào)到 屬性 models 數(shù)組裝有 asset 的 WFAlumbModel 的 WFCacheModel 的集合.這樣的對所有的相關對象層級管理.分開管理.

在控制器回調(diào)到數(shù)據(jù)源.然后給 cell 賦值的時候.在 cell 中調(diào)用方法,返回 UIImage, 賦值就可以了.

- (PHImageRequestID)getPhotosWithAsset:(id)asset
                         originalImage:(BOOL)originalImage
                            completion:(void (^)(UIImage *))completion
  • 在獲取到圖片的時候,我們要對圖片一些裁剪.在裁剪的方法中,只創(chuàng)建了 CGImageRef 對象,用完了后而沒有釋放,所以造成內(nèi)存泄露.從而引起 ReceiveMemoryWarning.

解決辦法:用完后釋放該對象.

CGImageRelease(imageRef);

做完這些優(yōu)化.現(xiàn)在內(nèi)存問題已經(jīng)不復存在了,性能也挺不錯的.前面對相冊管理的優(yōu)化由于代碼篇幅,沒有詳細的說明.有興趣的小伙伴可以下載 demo 看看,地址還是前面的那個,沒有變哦!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末线罕,一起剝皮案震驚了整個濱河市止潮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钞楼,老刑警劉巖喇闸,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異询件,居然都是意外死亡燃乍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門宛琅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刻蟹,“玉大人,你說我怎么就攤上這事夯秃∽兀” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵仓洼,是天一觀的道長介陶。 經(jīng)常有香客問我,道長色建,這世上最難降的妖魔是什么哺呜? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮箕戳,結果婚禮上某残,老公的妹妹穿的比我還像新娘国撵。我一直安慰自己,他們只是感情好玻墅,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布介牙。 她就那樣靜靜地躺著,像睡著了一般澳厢。 火紅的嫁衣襯著肌膚如雪环础。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天剩拢,我揣著相機與錄音线得,去河邊找鬼。 笑死徐伐,一個胖子當著我的面吹牛贯钩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播办素,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼角雷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了性穿?” 一聲冷哼從身側響起谓罗,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎季二,沒想到半個月后檩咱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡胯舷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年刻蚯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桑嘶。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡炊汹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逃顶,到底是詐尸還是另有隱情讨便,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布以政,位于F島的核電站霸褒,受9級特大地震影響,放射性物質發(fā)生泄漏盈蛮。R本人自食惡果不足惜废菱,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殊轴,春花似錦衰倦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孽文,卻和暖如春淹接,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叛溢。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劲适,地道東北人楷掉。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像霞势,于是被迫代替她去往敵國和親烹植。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容