TZImageManager.m 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. //
  2. // TZImageManager.m
  3. // TZImagePickerController
  4. //
  5. // Created by 谭真 on 16/1/4.
  6. // Copyright © 2016年 谭真. All rights reserved.
  7. //
  8. #import "TZImageManager.h"
  9. #import "TZAssetModel.h"
  10. #import "TZImagePickerController.h"
  11. #import <MobileCoreServices/MobileCoreServices.h>
  12. @interface TZImageManager ()
  13. #pragma clang diagnostic push
  14. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  15. @end
  16. @implementation TZImageManager
  17. CGSize AssetGridThumbnailSize;
  18. CGFloat TZScreenWidth;
  19. CGFloat TZScreenScale;
  20. static TZImageManager *manager;
  21. static dispatch_once_t onceToken;
  22. + (instancetype)manager {
  23. dispatch_once(&onceToken, ^{
  24. manager = [[self alloc] init];
  25. // manager.cachingImageManager = [[PHCachingImageManager alloc] init];
  26. // manager.cachingImageManager.allowsCachingHighQualityImages = YES;
  27. [manager configTZScreenWidth];
  28. });
  29. return manager;
  30. }
  31. + (void)deallocManager {
  32. onceToken = 0;
  33. manager = nil;
  34. }
  35. - (void)setPhotoWidth:(CGFloat)photoWidth {
  36. _photoWidth = photoWidth;
  37. TZScreenWidth = photoWidth / 2;
  38. }
  39. - (void)setColumnNumber:(NSInteger)columnNumber {
  40. [self configTZScreenWidth];
  41. _columnNumber = columnNumber;
  42. CGFloat margin = 4;
  43. CGFloat itemWH = (TZScreenWidth - 2 * margin - 4) / columnNumber - margin;
  44. AssetGridThumbnailSize = CGSizeMake(itemWH * TZScreenScale, itemWH * TZScreenScale);
  45. }
  46. - (void)configTZScreenWidth {
  47. TZScreenWidth = [UIScreen mainScreen].bounds.size.width;
  48. // 测试发现,如果scale在plus真机上取到3.0,内存会增大特别多。故这里写死成2.0
  49. TZScreenScale = 2.0;
  50. if (TZScreenWidth > 700) {
  51. TZScreenScale = 1.5;
  52. }
  53. }
  54. /// Return YES if Authorized 返回YES如果得到了授权
  55. - (BOOL)authorizationStatusAuthorized {
  56. if (self.isPreviewNetworkImage) {
  57. return YES;
  58. }
  59. NSInteger status = [PHPhotoLibrary authorizationStatus];
  60. if (status == 0) {
  61. /**
  62. * 当某些情况下AuthorizationStatus == AuthorizationStatusNotDetermined时,无法弹出系统首次使用的授权alertView,系统应用设置里亦没有相册的设置,此时将无法使用,故作以下操作,弹出系统首次使用的授权alertView
  63. */
  64. [self requestAuthorizationWithCompletion:nil];
  65. }
  66. return status == 3;
  67. }
  68. - (void)requestAuthorizationWithCompletion:(void (^)(void))completion {
  69. void (^callCompletionBlock)(void) = ^(){
  70. dispatch_async(dispatch_get_main_queue(), ^{
  71. if (completion) {
  72. completion();
  73. }
  74. });
  75. };
  76. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  77. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  78. callCompletionBlock();
  79. }];
  80. });
  81. }
  82. #pragma mark - Get Album
  83. - (void)getCameraRollAlbum:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion {
  84. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  85. config.allowPickingVideo = allowPickingVideo;
  86. config.allowPickingImage = allowPickingImage;
  87. [self getCameraRollAlbumWithFetchAssets:needFetchAssets completion:completion];
  88. }
  89. /// Get Album 获得相册/相册数组
  90. - (void)getCameraRollAlbumWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion {
  91. __block TZAlbumModel *model;
  92. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  93. PHFetchOptions *option = [[PHFetchOptions alloc] init];
  94. if (!config.allowPickingVideo) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
  95. if (!config.allowPickingImage) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld",
  96. PHAssetMediaTypeVideo];
  97. // option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"modificationDate" ascending:self.sortAscendingByModificationDate]];
  98. if (!self.sortAscendingByModificationDate) {
  99. option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:self.sortAscendingByModificationDate]];
  100. }
  101. PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
  102. for (PHAssetCollection *collection in smartAlbums) {
  103. // 有可能是PHCollectionList类的的对象,过滤掉
  104. if (![collection isKindOfClass:[PHAssetCollection class]]) continue;
  105. // 过滤空相册
  106. if (collection.estimatedAssetCount <= 0) continue;
  107. if ([self isCameraRollAlbum:collection]) {
  108. PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option];
  109. model = [self modelWithResult:fetchResult collection:collection isCameraRoll:YES needFetchAssets:needFetchAssets options:option];
  110. if (completion) completion(model);
  111. break;
  112. }
  113. }
  114. }
  115. - (void)getAllAlbums:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray<TZAlbumModel *> *))completion {
  116. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  117. config.allowPickingVideo = allowPickingVideo;
  118. config.allowPickingImage = allowPickingImage;
  119. [self getAllAlbumsWithFetchAssets:needFetchAssets completion:completion];
  120. }
  121. - (void)getAllAlbumsWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray<TZAlbumModel *> *))completion {
  122. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  123. NSMutableArray *albumArr = [NSMutableArray array];
  124. PHFetchOptions *option = [[PHFetchOptions alloc] init];
  125. if (!config.allowPickingVideo) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
  126. if (!config.allowPickingImage) option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld",
  127. PHAssetMediaTypeVideo];
  128. // option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"modificationDate" ascending:self.sortAscendingByModificationDate]];
  129. if (!self.sortAscendingByModificationDate) {
  130. option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:self.sortAscendingByModificationDate]];
  131. }
  132. // 我的照片流 1.6.10重新加入..
  133. PHFetchResult *myPhotoStreamAlbum = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumMyPhotoStream options:nil];
  134. PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
  135. PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
  136. PHFetchResult *syncedAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumSyncedAlbum options:nil];
  137. PHFetchResult *sharedAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumCloudShared options:nil];
  138. NSArray *allAlbums = @[myPhotoStreamAlbum,smartAlbums,topLevelUserCollections,syncedAlbums,sharedAlbums];
  139. for (PHFetchResult *fetchResult in allAlbums) {
  140. for (PHAssetCollection *collection in fetchResult) {
  141. // 有可能是PHCollectionList类的的对象,过滤掉
  142. if (![collection isKindOfClass:[PHAssetCollection class]]) continue;
  143. // 过滤空相册
  144. if (collection.estimatedAssetCount <= 0 && ![self isCameraRollAlbum:collection]) continue;
  145. PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option];
  146. if (fetchResult.count < 1 && ![self isCameraRollAlbum:collection]) continue;
  147. if ([self.pickerDelegate respondsToSelector:@selector(isAlbumCanSelect:result:)]) {
  148. if (![self.pickerDelegate isAlbumCanSelect:collection.localizedTitle result:fetchResult]) {
  149. continue;
  150. }
  151. }
  152. if (collection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumAllHidden) continue;
  153. if (collection.assetCollectionSubtype == 1000000201) continue; //『最近删除』相册
  154. if ([self isCameraRollAlbum:collection]) {
  155. [albumArr insertObject:[self modelWithResult:fetchResult collection:collection isCameraRoll:YES needFetchAssets:needFetchAssets options:option] atIndex:0];
  156. } else {
  157. [albumArr addObject:[self modelWithResult:fetchResult collection:collection isCameraRoll:NO needFetchAssets:needFetchAssets options:option]];
  158. }
  159. }
  160. }
  161. if (completion) {
  162. completion(albumArr);
  163. }
  164. }
  165. #pragma mark - Get Assets
  166. /// Get Assets 获得照片数组
  167. - (void)getAssetsFromFetchResult:(PHFetchResult *)result allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(NSArray<TZAssetModel *> *))completion {
  168. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  169. config.allowPickingVideo = allowPickingVideo;
  170. config.allowPickingImage = allowPickingImage;
  171. return [self getAssetsFromFetchResult:result completion:completion];
  172. }
  173. - (void)getAssetsFromFetchResult:(PHFetchResult *)result completion:(void (^)(NSArray<TZAssetModel *> *))completion {
  174. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  175. NSMutableArray *photoArr = [NSMutableArray array];
  176. [result enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL * _Nonnull stop) {
  177. TZAssetModel *model = [self assetModelWithAsset:asset allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage];
  178. if (model) {
  179. [photoArr addObject:model];
  180. }
  181. }];
  182. if (completion) completion(photoArr);
  183. }
  184. /// Get asset at index 获得下标为index的单个照片
  185. /// if index beyond bounds, return nil in callback 如果索引越界, 在回调中返回 nil
  186. - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(TZAssetModel *))completion {
  187. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  188. config.allowPickingVideo = allowPickingVideo;
  189. config.allowPickingImage = allowPickingImage;
  190. [self getAssetFromFetchResult:result atIndex:index allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage completion:completion];
  191. }
  192. - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index completion:(void (^)(TZAssetModel *))completion {
  193. PHAsset *asset;
  194. @try {
  195. asset = result[index];
  196. }
  197. @catch (NSException* e) {
  198. if (completion) completion(nil);
  199. return;
  200. }
  201. TZImagePickerConfig *config = [TZImagePickerConfig sharedInstance];
  202. TZAssetModel *model = [self assetModelWithAsset:asset allowPickingVideo:config.allowPickingVideo allowPickingImage:config.allowPickingImage];
  203. if (completion) completion(model);
  204. }
  205. - (TZAssetModel *)assetModelWithAsset:(PHAsset *)asset allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage {
  206. BOOL canSelect = YES;
  207. if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanSelect:)]) {
  208. canSelect = [self.pickerDelegate isAssetCanSelect:asset];
  209. }
  210. if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanBeDisplayed:)]){
  211. canSelect = [self.pickerDelegate isAssetCanBeDisplayed:asset];
  212. }
  213. if (!canSelect) return nil;
  214. TZAssetModel *model;
  215. TZAssetModelMediaType type = [self getAssetType:asset];
  216. if (!allowPickingVideo && type == TZAssetModelMediaTypeVideo) return nil;
  217. if (!allowPickingImage && type == TZAssetModelMediaTypePhoto) return nil;
  218. if (!allowPickingImage && type == TZAssetModelMediaTypePhotoGif) return nil;
  219. PHAsset *phAsset = (PHAsset *)asset;
  220. if (self.hideWhenCanNotSelect) {
  221. // 过滤掉尺寸不满足要求的图片
  222. if (![self isPhotoSelectableWithAsset:phAsset]) {
  223. return nil;
  224. }
  225. }
  226. NSString *timeLength = type == TZAssetModelMediaTypeVideo ? [NSString stringWithFormat:@"%0.0f",phAsset.duration] : @"";
  227. timeLength = [self getNewTimeFromDurationSecond:timeLength.integerValue];
  228. model = [TZAssetModel modelWithAsset:asset type:type timeLength:timeLength];
  229. return model;
  230. }
  231. - (TZAssetModelMediaType)getAssetType:(PHAsset *)asset {
  232. TZAssetModelMediaType type = TZAssetModelMediaTypePhoto;
  233. PHAsset *phAsset = (PHAsset *)asset;
  234. if (phAsset.mediaType == PHAssetMediaTypeVideo) type = TZAssetModelMediaTypeVideo;
  235. else if (phAsset.mediaType == PHAssetMediaTypeAudio) type = TZAssetModelMediaTypeAudio;
  236. else if (phAsset.mediaType == PHAssetMediaTypeImage) {
  237. if (@available(iOS 9.1, *)) {
  238. // if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) type = TZAssetModelMediaTypeLivePhoto;
  239. }
  240. // Gif
  241. if ([[phAsset valueForKey:@"filename"] hasSuffix:@"GIF"]) {
  242. type = TZAssetModelMediaTypePhotoGif;
  243. }
  244. }
  245. return type;
  246. }
  247. - (NSString *)getNewTimeFromDurationSecond:(NSInteger)duration {
  248. NSString *newTime;
  249. if (duration < 10) {
  250. newTime = [NSString stringWithFormat:@"0:0%zd",duration];
  251. } else if (duration < 60) {
  252. newTime = [NSString stringWithFormat:@"0:%zd",duration];
  253. } else {
  254. NSInteger min = duration / 60;
  255. NSInteger sec = duration - (min * 60);
  256. if (sec < 10) {
  257. newTime = [NSString stringWithFormat:@"%zd:0%zd",min,sec];
  258. } else {
  259. newTime = [NSString stringWithFormat:@"%zd:%zd",min,sec];
  260. }
  261. }
  262. return newTime;
  263. }
  264. /// Get photo bytes 获得一组照片的大小
  265. - (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *totalBytes))completion {
  266. if (!photos || !photos.count) {
  267. if (completion) completion(@"0B");
  268. return;
  269. }
  270. __block NSInteger dataLength = 0;
  271. __block NSInteger assetCount = 0;
  272. for (NSInteger i = 0; i < photos.count; i++) {
  273. TZAssetModel *model = photos[i];
  274. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  275. options.resizeMode = PHImageRequestOptionsResizeModeFast;
  276. options.networkAccessAllowed = YES;
  277. if (model.type == TZAssetModelMediaTypePhotoGif) {
  278. options.version = PHImageRequestOptionsVersionOriginal;
  279. }
  280. [[PHImageManager defaultManager] requestImageDataForAsset:model.asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
  281. if (model.type != TZAssetModelMediaTypeVideo) dataLength += imageData.length;
  282. assetCount ++;
  283. if (assetCount >= photos.count) {
  284. NSString *bytes = [self getBytesFromDataLength:dataLength];
  285. if (completion) completion(bytes);
  286. }
  287. }];
  288. }
  289. }
  290. - (NSString *)getBytesFromDataLength:(NSInteger)dataLength {
  291. NSString *bytes;
  292. if (dataLength >= 0.1 * (1024 * 1024)) {
  293. bytes = [NSString stringWithFormat:@"%0.1fM",dataLength/1024/1024.0];
  294. } else if (dataLength >= 1024) {
  295. bytes = [NSString stringWithFormat:@"%0.0fK",dataLength/1024.0];
  296. } else {
  297. bytes = [NSString stringWithFormat:@"%zdB",dataLength];
  298. }
  299. return bytes;
  300. }
  301. #pragma mark - Get Photo
  302. /// Get photo 获得照片本身
  303. - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *, NSDictionary *, BOOL isDegraded))completion {
  304. return [self getPhotoWithAsset:asset completion:completion progressHandler:nil networkAccessAllowed:YES];
  305. }
  306. - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion {
  307. return [self getPhotoWithAsset:asset photoWidth:photoWidth completion:completion progressHandler:nil networkAccessAllowed:YES];
  308. }
  309. - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed {
  310. CGFloat fullScreenWidth = TZScreenWidth;
  311. if (_photoPreviewMaxWidth > 0 && fullScreenWidth > _photoPreviewMaxWidth) {
  312. fullScreenWidth = _photoPreviewMaxWidth;
  313. }
  314. return [self getPhotoWithAsset:asset photoWidth:fullScreenWidth completion:completion progressHandler:progressHandler networkAccessAllowed:networkAccessAllowed];
  315. }
  316. - (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler {
  317. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  318. options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  319. dispatch_async(dispatch_get_main_queue(), ^{
  320. if (progressHandler) {
  321. progressHandler(progress, error, stop, info);
  322. }
  323. });
  324. };
  325. options.networkAccessAllowed = YES;
  326. options.resizeMode = PHImageRequestOptionsResizeModeFast;
  327. int32_t imageRequestID = [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
  328. if (completion) completion(imageData,dataUTI,orientation,info);
  329. }];
  330. return imageRequestID;
  331. }
  332. - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed {
  333. CGSize imageSize;
  334. if (photoWidth < TZScreenWidth && photoWidth < _photoPreviewMaxWidth) {
  335. imageSize = AssetGridThumbnailSize;
  336. } else {
  337. PHAsset *phAsset = (PHAsset *)asset;
  338. CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight;
  339. CGFloat pixelWidth = photoWidth * TZScreenScale;
  340. // 超宽图片
  341. if (aspectRatio > 1.8) {
  342. pixelWidth = pixelWidth * aspectRatio;
  343. }
  344. // 超高图片
  345. if (aspectRatio < 0.2) {
  346. pixelWidth = pixelWidth * 0.5;
  347. }
  348. CGFloat pixelHeight = pixelWidth / aspectRatio;
  349. imageSize = CGSizeMake(pixelWidth, pixelHeight);
  350. }
  351. // 修复获取图片时出现的瞬间内存过高问题
  352. // 下面两行代码,来自hsjcom,他的github是:https://github.com/hsjcom 表示感谢
  353. PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
  354. option.resizeMode = PHImageRequestOptionsResizeModeFast;
  355. int32_t imageRequestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage *result, NSDictionary *info) {
  356. BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue];
  357. if (!cancelled && result) {
  358. result = [self fixOrientation:result];
  359. if (completion) completion(result,info,[[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
  360. }
  361. // Download image from iCloud / 从iCloud下载图片
  362. if ([info objectForKey:PHImageResultIsInCloudKey] && !result && networkAccessAllowed) {
  363. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  364. options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  365. dispatch_async(dispatch_get_main_queue(), ^{
  366. if (progressHandler) {
  367. progressHandler(progress, error, stop, info);
  368. }
  369. });
  370. };
  371. options.networkAccessAllowed = YES;
  372. options.resizeMode = PHImageRequestOptionsResizeModeFast;
  373. [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
  374. UIImage *resultImage = [UIImage imageWithData:imageData];
  375. if (![TZImagePickerConfig sharedInstance].notScaleImage) {
  376. resultImage = [self scaleImage:resultImage toSize:imageSize];
  377. }
  378. if (!resultImage && result) {
  379. resultImage = result;
  380. }
  381. resultImage = [self fixOrientation:resultImage];
  382. if (completion) completion(resultImage,info,NO);
  383. }];
  384. }
  385. }];
  386. return imageRequestID;
  387. }
  388. /// Get postImage / 获取封面图
  389. - (PHImageRequestID)getPostImageWithAlbumModel:(TZAlbumModel *)model completion:(void (^)(UIImage *))completion {
  390. id asset = [model.result lastObject];
  391. if (!self.sortAscendingByModificationDate) {
  392. asset = [model.result firstObject];
  393. }
  394. if (!asset) {
  395. return -1;
  396. }
  397. return [[TZImageManager manager] getPhotoWithAsset:asset photoWidth:80 completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
  398. if (completion) completion(photo);
  399. }];
  400. }
  401. /// Get Original Photo / 获取原图
  402. - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info))completion {
  403. return [self getOriginalPhotoWithAsset:asset newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
  404. if (completion) {
  405. completion(photo,info);
  406. }
  407. }];
  408. }
  409. - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion {
  410. return [self getOriginalPhotoWithAsset:asset progressHandler:nil newCompletion:completion];
  411. }
  412. - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion {
  413. PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init];
  414. option.networkAccessAllowed = YES;
  415. if (progressHandler) {
  416. [option setProgressHandler:progressHandler];
  417. }
  418. option.resizeMode = PHImageRequestOptionsResizeModeFast;
  419. return [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
  420. BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue];
  421. if (!cancelled && imageData) {
  422. UIImage *result = [self fixOrientation:[UIImage imageWithData:imageData]];
  423. BOOL isDegraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue];
  424. if (completion) completion(result,info,isDegraded);
  425. }
  426. }];
  427. }
  428. - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion {
  429. return [self getOriginalPhotoDataWithAsset:asset progressHandler:nil completion:completion];
  430. }
  431. - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion {
  432. PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
  433. option.networkAccessAllowed = YES;
  434. if ([[asset valueForKey:@"filename"] hasSuffix:@"GIF"]) {
  435. // if version isn't PHImageRequestOptionsVersionOriginal, the gif may cann't play
  436. option.version = PHImageRequestOptionsVersionOriginal;
  437. }
  438. [option setProgressHandler:progressHandler];
  439. option.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
  440. return [[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
  441. BOOL cancelled = [[info objectForKey:PHImageCancelledKey] boolValue];
  442. if (!cancelled && imageData) {
  443. if (completion) completion(imageData,info,NO);
  444. }
  445. }];
  446. }
  447. - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL {
  448. AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
  449. if (!asset) {
  450. return nil;
  451. }
  452. AVAssetImageGenerator *generator =[[AVAssetImageGenerator alloc] initWithAsset:asset];
  453. generator.appliesPreferredTrackTransform = YES;
  454. generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
  455. CFTimeInterval time = 0.1;
  456. CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMake(time, 60) actualTime:NULL error:nil];
  457. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
  458. CGImageRelease(imageRef);
  459. return image;
  460. }
  461. #pragma mark - Save photo
  462. - (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion {
  463. [self savePhotoWithImage:image location:nil completion:completion];
  464. }
  465. - (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion {
  466. __block NSString *localIdentifier = nil;
  467. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
  468. PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
  469. localIdentifier = request.placeholderForCreatedAsset.localIdentifier;
  470. if (location) {
  471. request.location = location;
  472. }
  473. request.creationDate = [NSDate date];
  474. } completionHandler:^(BOOL success, NSError *error) {
  475. dispatch_async(dispatch_get_main_queue(), ^{
  476. if (success && completion && localIdentifier) {
  477. [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion];
  478. } else {
  479. if (error) {
  480. NSLog(@"保存照片出错:%@",error.localizedDescription);
  481. }
  482. if (completion) {
  483. completion(nil, error);
  484. }
  485. }
  486. });
  487. }];
  488. }
  489. - (void)savePhotoWithImage:(UIImage *)image meta:(NSDictionary *)meta location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion {
  490. NSData *imageData = UIImageJPEGRepresentation(image, 1.0f);
  491. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
  492. NSDateFormatter *formater = [[NSDateFormatter alloc] init];
  493. [formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss-SSS"];
  494. NSString *path = [NSTemporaryDirectory() stringByAppendingFormat:@"image-%@.jpg", [formater stringFromDate:[NSDate date]]];
  495. NSURL *tmpURL = [NSURL fileURLWithPath:path];
  496. CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)tmpURL, kUTTypeJPEG, 1, NULL);
  497. CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)meta);
  498. CGImageDestinationFinalize(destination);
  499. CFRelease(source);
  500. CFRelease(destination);
  501. __block NSString *localIdentifier = nil;
  502. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
  503. PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL];
  504. localIdentifier = request.placeholderForCreatedAsset.localIdentifier;
  505. if (location) {
  506. request.location = location;
  507. }
  508. request.creationDate = [NSDate date];
  509. } completionHandler:^(BOOL success, NSError *error) {
  510. [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
  511. dispatch_async(dispatch_get_main_queue(), ^{
  512. if (success && completion && localIdentifier) {
  513. [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion];
  514. } else {
  515. if (error) {
  516. NSLog(@"保存照片出错:%@",error.localizedDescription);
  517. }
  518. if (completion) {
  519. completion(nil, error);
  520. }
  521. }
  522. });
  523. }];
  524. }
  525. - (void)fetchAssetByIocalIdentifier:(NSString *)localIdentifier retryCount:(NSInteger)retryCount completion:(void (^)(PHAsset *asset, NSError *error))completion {
  526. PHAsset *asset = [[PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil] firstObject];
  527. if (asset || retryCount <= 0) {
  528. if (completion) {
  529. completion(asset, nil);
  530. }
  531. return;
  532. }
  533. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  534. [self fetchAssetByIocalIdentifier:localIdentifier retryCount:retryCount - 1 completion:completion];
  535. });
  536. }
  537. #pragma mark - Save video
  538. - (void)saveVideoWithUrl:(NSURL *)url completion:(void (^)(PHAsset *asset, NSError *error))completion {
  539. [self saveVideoWithUrl:url location:nil completion:completion];
  540. }
  541. - (void)saveVideoWithUrl:(NSURL *)url location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion {
  542. __block NSString *localIdentifier = nil;
  543. [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
  544. PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url];
  545. localIdentifier = request.placeholderForCreatedAsset.localIdentifier;
  546. if (location) {
  547. request.location = location;
  548. }
  549. request.creationDate = [NSDate date];
  550. } completionHandler:^(BOOL success, NSError *error) {
  551. dispatch_async(dispatch_get_main_queue(), ^{
  552. if (success && completion && localIdentifier) {
  553. [self fetchAssetByIocalIdentifier:localIdentifier retryCount:10 completion:completion];
  554. } else {
  555. if (error) {
  556. NSLog(@"保存视频出错:%@",error.localizedDescription);
  557. }
  558. if (completion) {
  559. completion(nil, error);
  560. }
  561. }
  562. });
  563. }];
  564. }
  565. #pragma mark - Get Video
  566. /// Get Video / 获取视频
  567. - (void)getVideoWithAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem *, NSDictionary *))completion {
  568. [self getVideoWithAsset:asset progressHandler:nil completion:completion];
  569. }
  570. - (void)getVideoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(AVPlayerItem *, NSDictionary *))completion {
  571. PHVideoRequestOptions *option = [[PHVideoRequestOptions alloc] init];
  572. option.networkAccessAllowed = YES;
  573. option.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  574. dispatch_async(dispatch_get_main_queue(), ^{
  575. if (progressHandler) {
  576. progressHandler(progress, error, stop, info);
  577. }
  578. });
  579. };
  580. [[PHImageManager defaultManager] requestPlayerItemForVideo:asset options:option resultHandler:^(AVPlayerItem *playerItem, NSDictionary *info) {
  581. if (completion) completion(playerItem,info);
  582. }];
  583. }
  584. #pragma mark - Export video
  585. /// Export Video / 导出视频
  586. - (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  587. [self getVideoOutputPathWithAsset:asset presetName:AVAssetExportPresetMediumQuality success:success failure:failure];
  588. }
  589. - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  590. [self getVideoOutputPathWithAsset:asset presetName:presetName timeRange:kCMTimeRangeZero success:success failure:failure];
  591. }
  592. - (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  593. [self startExportVideoWithVideoAsset:videoAsset timeRange:kCMTimeRangeZero presetName:presetName success:success failure:failure];
  594. }
  595. - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  596. if (@available(iOS 14.0, *)) {
  597. [self requestVideoOutputPathWithAsset:asset presetName:presetName timeRange:timeRange success:success failure:failure];
  598. return;
  599. }
  600. [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:[self getVideoRequestOptions] resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){
  601. // NSLog(@"Info:\n%@",info);
  602. AVURLAsset *videoAsset = (AVURLAsset*)avasset;
  603. // NSLog(@"AVAsset URL: %@",myAsset.URL);
  604. [self startExportVideoWithVideoAsset:videoAsset timeRange:timeRange presetName:presetName success:success failure:failure];
  605. }];
  606. }
  607. - (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset timeRange:(CMTimeRange)timeRange presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  608. if (!presetName) {
  609. presetName = AVAssetExportPresetMediumQuality;
  610. }
  611. // Find compatible presets by video asset.
  612. NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
  613. // Begin to compress video
  614. // Now we just compress to low resolution if it supports
  615. // If you need to upload to the server, but server does't support to upload by streaming,
  616. // You can compress the resolution to lower. Or you can support more higher resolution.
  617. if ([presets containsObject:presetName]) {
  618. AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:presetName];
  619. NSString *outputPath = [self getVideoOutputPath];
  620. // Optimize for network use.
  621. session.shouldOptimizeForNetworkUse = true;
  622. if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) {
  623. session.timeRange = timeRange;
  624. }
  625. NSArray *supportedTypeArray = session.supportedFileTypes;
  626. if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
  627. session.outputFileType = AVFileTypeMPEG4;
  628. } else if (supportedTypeArray.count == 0) {
  629. if (failure) {
  630. failure(@"该视频类型暂不支持导出", nil);
  631. }
  632. NSLog(@"No supported file types 视频类型暂不支持导出");
  633. return;
  634. } else {
  635. session.outputFileType = [supportedTypeArray objectAtIndex:0];
  636. if (videoAsset.URL && videoAsset.URL.lastPathComponent) {
  637. outputPath = [outputPath stringByReplacingOccurrencesOfString:@".mp4" withString:[NSString stringWithFormat:@"-%@", videoAsset.URL.lastPathComponent]];
  638. }
  639. }
  640. // NSLog(@"video outputPath = %@",outputPath);
  641. session.outputURL = [NSURL fileURLWithPath:outputPath];
  642. if (![[NSFileManager defaultManager] fileExistsAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"]]) {
  643. [[NSFileManager defaultManager] createDirectoryAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"] withIntermediateDirectories:YES attributes:nil error:nil];
  644. }
  645. if ([TZImagePickerConfig sharedInstance].needFixComposition) {
  646. AVMutableVideoComposition *videoComposition = [self fixedCompositionWithAsset:videoAsset];
  647. if (videoComposition.renderSize.width) {
  648. // 修正视频转向
  649. session.videoComposition = videoComposition;
  650. }
  651. }
  652. // Begin to export video to the output path asynchronously.
  653. [session exportAsynchronouslyWithCompletionHandler:^(void) {
  654. [self handleVideoExportResult:session outputPath:outputPath success:success failure:failure];
  655. }];
  656. } else {
  657. if (failure) {
  658. NSString *errorMessage = [NSString stringWithFormat:@"当前设备不支持该预设:%@", presetName];
  659. failure(errorMessage, nil);
  660. }
  661. }
  662. }
  663. - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  664. [self requestVideoOutputPathWithAsset:asset presetName:presetName timeRange:kCMTimeRangeZero success:success failure:failure];
  665. }
  666. - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  667. if (!presetName) {
  668. presetName = AVAssetExportPresetMediumQuality;
  669. }
  670. [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:[self getVideoRequestOptions] exportPreset:presetName resultHandler:^(AVAssetExportSession *_Nullable exportSession, NSDictionary *_Nullable info) {
  671. NSString *outputPath = [self getVideoOutputPath];
  672. exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
  673. exportSession.shouldOptimizeForNetworkUse = NO;
  674. exportSession.outputFileType = AVFileTypeMPEG4;
  675. if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) {
  676. exportSession.timeRange = timeRange;
  677. }
  678. [exportSession exportAsynchronouslyWithCompletionHandler:^{
  679. [self handleVideoExportResult:exportSession outputPath:outputPath success:success failure:failure];
  680. }];
  681. }];
  682. }
  683. - (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure {
  684. [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:[self getVideoRequestOptions] resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){
  685. // NSLog(@"AVAsset URL: %@",myAsset.URL);
  686. if ([avasset isKindOfClass:[AVURLAsset class]]) {
  687. NSURL *url = [(AVURLAsset *)avasset URL];
  688. if (success) {
  689. success(url);
  690. }
  691. } else if (failure) {
  692. failure(info);
  693. }
  694. }];
  695. }
  696. - (void)handleVideoExportResult:(AVAssetExportSession *)session outputPath:(NSString *)outputPath success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  697. dispatch_async(dispatch_get_main_queue(), ^{
  698. switch (session.status) {
  699. case AVAssetExportSessionStatusUnknown: {
  700. NSLog(@"AVAssetExportSessionStatusUnknown");
  701. } break;
  702. case AVAssetExportSessionStatusWaiting: {
  703. NSLog(@"AVAssetExportSessionStatusWaiting");
  704. } break;
  705. case AVAssetExportSessionStatusExporting: {
  706. NSLog(@"AVAssetExportSessionStatusExporting");
  707. } break;
  708. case AVAssetExportSessionStatusCompleted: {
  709. NSLog(@"AVAssetExportSessionStatusCompleted");
  710. if (success) {
  711. success(outputPath);
  712. }
  713. } break;
  714. case AVAssetExportSessionStatusFailed: {
  715. NSLog(@"AVAssetExportSessionStatusFailed");
  716. if (failure) {
  717. failure(@"视频导出失败", session.error);
  718. }
  719. } break;
  720. case AVAssetExportSessionStatusCancelled: {
  721. NSLog(@"AVAssetExportSessionStatusCancelled");
  722. if (failure) {
  723. failure(@"导出任务已被取消", nil);
  724. }
  725. } break;
  726. default: break;
  727. }
  728. });
  729. }
  730. - (PHVideoRequestOptions *)getVideoRequestOptions {
  731. PHVideoRequestOptions* options = [[PHVideoRequestOptions alloc] init];
  732. options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
  733. options.networkAccessAllowed = YES;
  734. return options;
  735. }
  736. - (NSString *)getVideoOutputPath {
  737. NSDateFormatter *formater = [[NSDateFormatter alloc] init];
  738. [formater setDateFormat:@"yyyy-MM-dd-HH-mm-ss-SSS"];
  739. NSString *outputPath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/video-%@-%d.mp4", [formater stringFromDate:[NSDate date]], arc4random_uniform(10000000)];
  740. return outputPath;
  741. }
  742. - (BOOL)isCameraRollAlbum:(PHAssetCollection *)metadata {
  743. NSString *versionStr = [[UIDevice currentDevice].systemVersion stringByReplacingOccurrencesOfString:@"." withString:@""];
  744. if (versionStr.length <= 1) {
  745. versionStr = [versionStr stringByAppendingString:@"00"];
  746. } else if (versionStr.length <= 2) {
  747. versionStr = [versionStr stringByAppendingString:@"0"];
  748. }
  749. CGFloat version = versionStr.floatValue;
  750. // 目前已知8.0.0 ~ 8.0.2系统,拍照后的图片会保存在最近添加中
  751. if (version >= 800 && version <= 802) {
  752. return ((PHAssetCollection *)metadata).assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumRecentlyAdded;
  753. } else {
  754. return ((PHAssetCollection *)metadata).assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary;
  755. }
  756. }
  757. /// 检查照片大小是否满足最小要求
  758. - (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset {
  759. CGSize photoSize = CGSizeMake(asset.pixelWidth, asset.pixelHeight);
  760. if (self.minPhotoWidthSelectable > photoSize.width || self.minPhotoHeightSelectable > photoSize.height) {
  761. return NO;
  762. }
  763. return YES;
  764. }
  765. /// 检查照片能否被选中
  766. - (BOOL)isAssetCannotBeSelected:(PHAsset *)asset {
  767. if ([self.pickerDelegate respondsToSelector:@selector(isAssetCanBeSelected:)]) {
  768. BOOL canSelectAsset = [self.pickerDelegate isAssetCanBeSelected:asset];
  769. return !canSelectAsset;
  770. }
  771. return NO;
  772. }
  773. #pragma mark - Private Method
  774. - (TZAlbumModel *)modelWithResult:(PHFetchResult *)result collection:(PHAssetCollection *)collection isCameraRoll:(BOOL)isCameraRoll needFetchAssets:(BOOL)needFetchAssets options:(PHFetchOptions *)options {
  775. TZAlbumModel *model = [[TZAlbumModel alloc] init];
  776. [model setResult:result needFetchAssets:needFetchAssets];
  777. model.name = collection.localizedTitle;
  778. model.collection = collection;
  779. model.options = options;
  780. model.isCameraRoll = isCameraRoll;
  781. model.count = result.count;
  782. return model;
  783. }
  784. /// 缩放图片至新尺寸
  785. - (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size {
  786. if (image.size.width > size.width) {
  787. UIGraphicsBeginImageContext(size);
  788. [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
  789. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  790. UIGraphicsEndImageContext();
  791. return newImage;
  792. /* 好像不怎么管用:https://mp.weixin.qq.com/s/CiqMlEIp1Ir2EJSDGgMooQ
  793. CGFloat maxPixelSize = MAX(size.width, size.height);
  794. CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)UIImageJPEGRepresentation(image, 0.9), nil);
  795. NSDictionary *options = @{(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways:(__bridge id)kCFBooleanTrue,
  796. (__bridge id)kCGImageSourceThumbnailMaxPixelSize:[NSNumber numberWithFloat:maxPixelSize]
  797. };
  798. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
  799. UIImage *newImage = [UIImage imageWithCGImage:imageRef scale:2 orientation:image.imageOrientation];
  800. CGImageRelease(imageRef);
  801. CFRelease(sourceRef);
  802. return newImage;
  803. */
  804. } else {
  805. return image;
  806. }
  807. }
  808. /// 判断asset是否是视频
  809. - (BOOL)isVideo:(PHAsset *)asset {
  810. return asset.mediaType == PHAssetMediaTypeVideo;
  811. }
  812. - (TZAssetModel *)createModelWithAsset:(PHAsset *)asset {
  813. TZAssetModelMediaType type = [[TZImageManager manager] getAssetType:asset];
  814. NSString *timeLength = type == TZAssetModelMediaTypeVideo ? [NSString stringWithFormat:@"%0.0f",asset.duration] : @"";
  815. timeLength = [[TZImageManager manager] getNewTimeFromDurationSecond:timeLength.integerValue];
  816. TZAssetModel *model = [TZAssetModel modelWithAsset:asset type:type timeLength:timeLength];
  817. return model;
  818. }
  819. /// 获取优化后的视频转向信息
  820. - (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset {
  821. AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
  822. // 视频转向
  823. int degrees = [self degressFromVideoFileWithAsset:videoAsset];
  824. if (degrees != 0) {
  825. CGAffineTransform translateToCenter;
  826. CGAffineTransform mixedTransform;
  827. videoComposition.frameDuration = CMTimeMake(1, 30);
  828. NSArray *tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
  829. AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
  830. AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
  831. roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
  832. AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
  833. if (degrees == 90) {
  834. // 顺时针旋转90°
  835. translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.height, 0.0);
  836. mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2);
  837. videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);
  838. [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
  839. } else if(degrees == 180){
  840. // 顺时针旋转180°
  841. translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.width, videoTrack.naturalSize.height);
  842. mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI);
  843. videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width,videoTrack.naturalSize.height);
  844. [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
  845. } else if(degrees == 270){
  846. // 顺时针旋转270°
  847. translateToCenter = CGAffineTransformMakeTranslation(0.0, videoTrack.naturalSize.width);
  848. mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0);
  849. videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);
  850. [roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
  851. }
  852. roateInstruction.layerInstructions = @[roateLayerInstruction];
  853. // 加入视频方向信息
  854. videoComposition.instructions = @[roateInstruction];
  855. }
  856. return videoComposition;
  857. }
  858. /// 获取视频角度
  859. - (int)degressFromVideoFileWithAsset:(AVAsset *)asset {
  860. int degress = 0;
  861. NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  862. if([tracks count] > 0) {
  863. AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
  864. CGAffineTransform t = videoTrack.preferredTransform;
  865. if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
  866. // Portrait
  867. degress = 90;
  868. } else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
  869. // PortraitUpsideDown
  870. degress = 270;
  871. } else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
  872. // LandscapeRight
  873. degress = 0;
  874. } else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
  875. // LandscapeLeft
  876. degress = 180;
  877. }
  878. }
  879. return degress;
  880. }
  881. /// 修正图片转向
  882. - (UIImage *)fixOrientation:(UIImage *)aImage {
  883. if (!self.shouldFixOrientation) return aImage;
  884. // No-op if the orientation is already correct
  885. if (aImage.imageOrientation == UIImageOrientationUp)
  886. return aImage;
  887. // We need to calculate the proper transformation to make the image upright.
  888. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
  889. CGAffineTransform transform = CGAffineTransformIdentity;
  890. switch (aImage.imageOrientation) {
  891. case UIImageOrientationDown:
  892. case UIImageOrientationDownMirrored:
  893. transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height);
  894. transform = CGAffineTransformRotate(transform, M_PI);
  895. break;
  896. case UIImageOrientationLeft:
  897. case UIImageOrientationLeftMirrored:
  898. transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
  899. transform = CGAffineTransformRotate(transform, M_PI_2);
  900. break;
  901. case UIImageOrientationRight:
  902. case UIImageOrientationRightMirrored:
  903. transform = CGAffineTransformTranslate(transform, 0, aImage.size.height);
  904. transform = CGAffineTransformRotate(transform, -M_PI_2);
  905. break;
  906. default:
  907. break;
  908. }
  909. switch (aImage.imageOrientation) {
  910. case UIImageOrientationUpMirrored:
  911. case UIImageOrientationDownMirrored:
  912. transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
  913. transform = CGAffineTransformScale(transform, -1, 1);
  914. break;
  915. case UIImageOrientationLeftMirrored:
  916. case UIImageOrientationRightMirrored:
  917. transform = CGAffineTransformTranslate(transform, aImage.size.height, 0);
  918. transform = CGAffineTransformScale(transform, -1, 1);
  919. break;
  920. default:
  921. break;
  922. }
  923. // Now we draw the underlying CGImage into a new context, applying the transform
  924. // calculated above.
  925. CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height,
  926. CGImageGetBitsPerComponent(aImage.CGImage), 0,
  927. CGImageGetColorSpace(aImage.CGImage),
  928. CGImageGetBitmapInfo(aImage.CGImage));
  929. CGContextConcatCTM(ctx, transform);
  930. switch (aImage.imageOrientation) {
  931. case UIImageOrientationLeft:
  932. case UIImageOrientationLeftMirrored:
  933. case UIImageOrientationRight:
  934. case UIImageOrientationRightMirrored:
  935. // Grr...
  936. CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage);
  937. break;
  938. default:
  939. CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage);
  940. break;
  941. }
  942. // And now we just create a new UIImage from the drawing context
  943. CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
  944. UIImage *img = [UIImage imageWithCGImage:cgimg];
  945. CGContextRelease(ctx);
  946. CGImageRelease(cgimg);
  947. return img;
  948. }
  949. #pragma clang diagnostic pop
  950. @end
  951. //@implementation TZSortDescriptor
  952. //
  953. //- (id)reversedSortDescriptor {
  954. // return [NSNumber numberWithBool:![TZImageManager manager].sortAscendingByModificationDate];
  955. //}
  956. //
  957. //@end