猎豆优选

QBAssetsViewController.m 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. //
  2. // QBAssetsViewController.m
  3. // QBImagePicker
  4. //
  5. // Created by Katsuma Tanaka on 2015/04/03.
  6. // Copyright (c) 2015 Katsuma Tanaka. All rights reserved.
  7. //
  8. #import "QBAssetsViewController.h"
  9. #import <Photos/Photos.h>
  10. // Views
  11. #import "QBImagePickerController.h"
  12. #import "QBAssetCell.h"
  13. #import "QBVideoIndicatorView.h"
  14. static CGSize CGSizeScale(CGSize size, CGFloat scale) {
  15. return CGSizeMake(size.width * scale, size.height * scale);
  16. }
  17. @interface QBImagePickerController (Private)
  18. @property (nonatomic, strong) NSBundle *assetBundle;
  19. @end
  20. @implementation NSIndexSet (Convenience)
  21. - (NSArray *)qb_indexPathsFromIndexesWithSection:(NSUInteger)section
  22. {
  23. NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
  24. [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  25. [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
  26. }];
  27. return indexPaths;
  28. }
  29. @end
  30. @implementation UICollectionView (Convenience)
  31. - (NSArray *)qb_indexPathsForElementsInRect:(CGRect)rect
  32. {
  33. NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
  34. if (allLayoutAttributes.count == 0) { return nil; }
  35. NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
  36. for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
  37. NSIndexPath *indexPath = layoutAttributes.indexPath;
  38. [indexPaths addObject:indexPath];
  39. }
  40. return indexPaths;
  41. }
  42. @end
  43. @interface QBAssetsViewController () <PHPhotoLibraryChangeObserver, UICollectionViewDelegateFlowLayout>
  44. @property (nonatomic, strong) IBOutlet UIBarButtonItem *doneButton;
  45. @property (nonatomic, strong) PHFetchResult *fetchResult;
  46. @property (nonatomic, strong) PHCachingImageManager *imageManager;
  47. @property (nonatomic, assign) CGRect previousPreheatRect;
  48. @property (nonatomic, assign) BOOL disableScrollToBottom;
  49. @property (nonatomic, strong) NSIndexPath *lastSelectedItemIndexPath;
  50. @end
  51. @implementation QBAssetsViewController
  52. - (void)viewDidLoad
  53. {
  54. [super viewDidLoad];
  55. [self setUpToolbarItems];
  56. [self resetCachedAssets];
  57. // Register observer
  58. [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
  59. }
  60. - (void)viewWillAppear:(BOOL)animated
  61. {
  62. [super viewWillAppear:animated];
  63. // Configure navigation item
  64. self.navigationItem.title = self.assetCollection.localizedTitle;
  65. self.navigationItem.prompt = self.imagePickerController.prompt;
  66. // Configure collection view
  67. self.collectionView.allowsMultipleSelection = self.imagePickerController.allowsMultipleSelection;
  68. // Show/hide 'Done' button
  69. if (self.imagePickerController.allowsMultipleSelection) {
  70. [self.navigationItem setRightBarButtonItem:self.doneButton animated:NO];
  71. } else {
  72. [self.navigationItem setRightBarButtonItem:nil animated:NO];
  73. }
  74. [self updateDoneButtonState];
  75. [self updateSelectionInfo];
  76. [self.collectionView reloadData];
  77. // Scroll to bottom
  78. if (self.fetchResult.count > 0 && self.isMovingToParentViewController && !self.disableScrollToBottom) {
  79. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(self.fetchResult.count - 1) inSection:0];
  80. [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
  81. }
  82. }
  83. - (void)viewWillDisappear:(BOOL)animated
  84. {
  85. [super viewWillDisappear:animated];
  86. self.disableScrollToBottom = YES;
  87. }
  88. - (void)viewDidAppear:(BOOL)animated
  89. {
  90. [super viewDidAppear:animated];
  91. self.disableScrollToBottom = NO;
  92. [self updateCachedAssets];
  93. }
  94. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  95. {
  96. // Save indexPath for the last item
  97. NSIndexPath *indexPath = [[self.collectionView indexPathsForVisibleItems] lastObject];
  98. // Update layout
  99. [self.collectionViewLayout invalidateLayout];
  100. // Restore scroll position
  101. [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  102. [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];
  103. }];
  104. }
  105. - (void)dealloc
  106. {
  107. // Deregister observer
  108. [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
  109. }
  110. #pragma mark - Accessors
  111. - (void)setAssetCollection:(PHAssetCollection *)assetCollection
  112. {
  113. _assetCollection = assetCollection;
  114. [self updateFetchRequest];
  115. [self.collectionView reloadData];
  116. }
  117. - (PHCachingImageManager *)imageManager
  118. {
  119. if (_imageManager == nil) {
  120. _imageManager = [PHCachingImageManager new];
  121. }
  122. return _imageManager;
  123. }
  124. - (BOOL)isAutoDeselectEnabled
  125. {
  126. return (self.imagePickerController.maximumNumberOfSelection == 1
  127. && self.imagePickerController.maximumNumberOfSelection >= self.imagePickerController.minimumNumberOfSelection);
  128. }
  129. #pragma mark - Actions
  130. - (IBAction)done:(id)sender
  131. {
  132. if ([self.imagePickerController.delegate respondsToSelector:@selector(qb_imagePickerController:didFinishPickingAssets:)]) {
  133. [self.imagePickerController.delegate qb_imagePickerController:self.imagePickerController
  134. didFinishPickingAssets:self.imagePickerController.selectedAssets.array];
  135. }
  136. }
  137. #pragma mark - Toolbar
  138. - (void)setUpToolbarItems
  139. {
  140. // Space
  141. UIBarButtonItem *leftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL];
  142. UIBarButtonItem *rightSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL];
  143. // Info label
  144. NSDictionary *attributes = @{ NSForegroundColorAttributeName: [UIColor blackColor] };
  145. UIBarButtonItem *infoButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL];
  146. infoButtonItem.enabled = NO;
  147. [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal];
  148. [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateDisabled];
  149. self.toolbarItems = @[leftSpace, infoButtonItem, rightSpace];
  150. }
  151. - (void)updateSelectionInfo
  152. {
  153. NSMutableOrderedSet *selectedAssets = self.imagePickerController.selectedAssets;
  154. if (selectedAssets.count > 0) {
  155. NSBundle *bundle = self.imagePickerController.assetBundle;
  156. NSString *format;
  157. if (selectedAssets.count > 1) {
  158. format = NSLocalizedStringFromTableInBundle(@"assets.toolbar.items-selected", @"QBImagePicker", bundle, nil);
  159. } else {
  160. format = NSLocalizedStringFromTableInBundle(@"assets.toolbar.item-selected", @"QBImagePicker", bundle, nil);
  161. }
  162. NSString *title = [NSString stringWithFormat:format, selectedAssets.count];
  163. [(UIBarButtonItem *)self.toolbarItems[1] setTitle:title];
  164. } else {
  165. [(UIBarButtonItem *)self.toolbarItems[1] setTitle:@""];
  166. }
  167. }
  168. #pragma mark - Fetching Assets
  169. - (void)updateFetchRequest
  170. {
  171. if (self.assetCollection) {
  172. PHFetchOptions *options = [PHFetchOptions new];
  173. switch (self.imagePickerController.mediaType) {
  174. case QBImagePickerMediaTypeImage:
  175. options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
  176. break;
  177. case QBImagePickerMediaTypeVideo:
  178. options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo];
  179. break;
  180. default:
  181. break;
  182. }
  183. self.fetchResult = [PHAsset fetchAssetsInAssetCollection:self.assetCollection options:options];
  184. if ([self isAutoDeselectEnabled] && self.imagePickerController.selectedAssets.count > 0) {
  185. // Get index of previous selected asset
  186. PHAsset *asset = [self.imagePickerController.selectedAssets firstObject];
  187. NSInteger assetIndex = [self.fetchResult indexOfObject:asset];
  188. self.lastSelectedItemIndexPath = [NSIndexPath indexPathForItem:assetIndex inSection:0];
  189. }
  190. } else {
  191. self.fetchResult = nil;
  192. }
  193. }
  194. #pragma mark - Checking for Selection Limit
  195. - (BOOL)isMinimumSelectionLimitFulfilled
  196. {
  197. return (self.imagePickerController.minimumNumberOfSelection <= self.imagePickerController.selectedAssets.count);
  198. }
  199. - (BOOL)isMaximumSelectionLimitReached
  200. {
  201. NSUInteger minimumNumberOfSelection = MAX(1, self.imagePickerController.minimumNumberOfSelection);
  202. if (minimumNumberOfSelection <= self.imagePickerController.maximumNumberOfSelection) {
  203. return (self.imagePickerController.maximumNumberOfSelection <= self.imagePickerController.selectedAssets.count);
  204. }
  205. return NO;
  206. }
  207. - (void)updateDoneButtonState
  208. {
  209. self.doneButton.enabled = [self isMinimumSelectionLimitFulfilled];
  210. }
  211. #pragma mark - Asset Caching
  212. - (void)resetCachedAssets
  213. {
  214. [self.imageManager stopCachingImagesForAllAssets];
  215. self.previousPreheatRect = CGRectZero;
  216. }
  217. - (void)updateCachedAssets
  218. {
  219. BOOL isViewVisible = [self isViewLoaded] && self.view.window != nil;
  220. if (!isViewVisible) { return; }
  221. // The preheat window is twice the height of the visible rect
  222. CGRect preheatRect = self.collectionView.bounds;
  223. preheatRect = CGRectInset(preheatRect, 0.0, -0.5 * CGRectGetHeight(preheatRect));
  224. // If scrolled by a "reasonable" amount...
  225. CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
  226. if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0) {
  227. // Compute the assets to start caching and to stop caching
  228. NSMutableArray *addedIndexPaths = [NSMutableArray array];
  229. NSMutableArray *removedIndexPaths = [NSMutableArray array];
  230. [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect addedHandler:^(CGRect addedRect) {
  231. NSArray *indexPaths = [self.collectionView qb_indexPathsForElementsInRect:addedRect];
  232. [addedIndexPaths addObjectsFromArray:indexPaths];
  233. } removedHandler:^(CGRect removedRect) {
  234. NSArray *indexPaths = [self.collectionView qb_indexPathsForElementsInRect:removedRect];
  235. [removedIndexPaths addObjectsFromArray:indexPaths];
  236. }];
  237. NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
  238. NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
  239. CGSize itemSize = [(UICollectionViewFlowLayout *)self.collectionViewLayout itemSize];
  240. CGSize targetSize = CGSizeScale(itemSize, self.traitCollection.displayScale);
  241. [self.imageManager startCachingImagesForAssets:assetsToStartCaching
  242. targetSize:targetSize
  243. contentMode:PHImageContentModeAspectFill
  244. options:nil];
  245. [self.imageManager stopCachingImagesForAssets:assetsToStopCaching
  246. targetSize:targetSize
  247. contentMode:PHImageContentModeAspectFill
  248. options:nil];
  249. self.previousPreheatRect = preheatRect;
  250. }
  251. }
  252. - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect addedHandler:(void (^)(CGRect addedRect))addedHandler removedHandler:(void (^)(CGRect removedRect))removedHandler
  253. {
  254. if (CGRectIntersectsRect(newRect, oldRect)) {
  255. CGFloat oldMaxY = CGRectGetMaxY(oldRect);
  256. CGFloat oldMinY = CGRectGetMinY(oldRect);
  257. CGFloat newMaxY = CGRectGetMaxY(newRect);
  258. CGFloat newMinY = CGRectGetMinY(newRect);
  259. if (newMaxY > oldMaxY) {
  260. CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
  261. addedHandler(rectToAdd);
  262. }
  263. if (oldMinY > newMinY) {
  264. CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
  265. addedHandler(rectToAdd);
  266. }
  267. if (newMaxY < oldMaxY) {
  268. CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
  269. removedHandler(rectToRemove);
  270. }
  271. if (oldMinY < newMinY) {
  272. CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
  273. removedHandler(rectToRemove);
  274. }
  275. } else {
  276. addedHandler(newRect);
  277. removedHandler(oldRect);
  278. }
  279. }
  280. - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
  281. {
  282. if (indexPaths.count == 0) { return nil; }
  283. NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
  284. for (NSIndexPath *indexPath in indexPaths) {
  285. if (indexPath.item < self.fetchResult.count) {
  286. PHAsset *asset = self.fetchResult[indexPath.item];
  287. [assets addObject:asset];
  288. }
  289. }
  290. return assets;
  291. }
  292. #pragma mark - PHPhotoLibraryChangeObserver
  293. - (void)photoLibraryDidChange:(PHChange *)changeInstance
  294. {
  295. dispatch_async(dispatch_get_main_queue(), ^{
  296. PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.fetchResult];
  297. if (collectionChanges) {
  298. // Get the new fetch result
  299. self.fetchResult = [collectionChanges fetchResultAfterChanges];
  300. if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
  301. // We need to reload all if the incremental diffs are not available
  302. [self.collectionView reloadData];
  303. } else {
  304. // If we have incremental diffs, tell the collection view to animate insertions and deletions
  305. [self.collectionView performBatchUpdates:^{
  306. NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
  307. if ([removedIndexes count]) {
  308. [self.collectionView deleteItemsAtIndexPaths:[removedIndexes qb_indexPathsFromIndexesWithSection:0]];
  309. }
  310. NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
  311. if ([insertedIndexes count]) {
  312. [self.collectionView insertItemsAtIndexPaths:[insertedIndexes qb_indexPathsFromIndexesWithSection:0]];
  313. }
  314. NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
  315. if ([changedIndexes count]) {
  316. [self.collectionView reloadItemsAtIndexPaths:[changedIndexes qb_indexPathsFromIndexesWithSection:0]];
  317. }
  318. } completion:NULL];
  319. }
  320. [self resetCachedAssets];
  321. }
  322. });
  323. }
  324. #pragma mark - UIScrollViewDelegate
  325. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  326. {
  327. [self updateCachedAssets];
  328. }
  329. #pragma mark - UICollectionViewDataSource
  330. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
  331. {
  332. return 1;
  333. }
  334. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  335. {
  336. return self.fetchResult.count;
  337. }
  338. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  339. {
  340. QBAssetCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AssetCell" forIndexPath:indexPath];
  341. cell.tag = indexPath.item;
  342. cell.showsOverlayViewWhenSelected = self.imagePickerController.allowsMultipleSelection;
  343. // Image
  344. PHAsset *asset = self.fetchResult[indexPath.item];
  345. CGSize itemSize = [(UICollectionViewFlowLayout *)collectionView.collectionViewLayout itemSize];
  346. CGSize targetSize = CGSizeScale(itemSize, self.traitCollection.displayScale);
  347. [self.imageManager requestImageForAsset:asset
  348. targetSize:targetSize
  349. contentMode:PHImageContentModeAspectFill
  350. options:nil
  351. resultHandler:^(UIImage *result, NSDictionary *info) {
  352. if (cell.tag == indexPath.item) {
  353. cell.imageView.image = result;
  354. }
  355. }];
  356. // Video indicator
  357. if (asset.mediaType == PHAssetMediaTypeVideo) {
  358. cell.videoIndicatorView.hidden = NO;
  359. NSInteger minutes = (NSInteger)(asset.duration / 60.0);
  360. NSInteger seconds = (NSInteger)ceil(asset.duration - 60.0 * (double)minutes);
  361. cell.videoIndicatorView.timeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
  362. if (asset.mediaSubtypes & PHAssetMediaSubtypeVideoHighFrameRate) {
  363. cell.videoIndicatorView.videoIcon.hidden = YES;
  364. cell.videoIndicatorView.slomoIcon.hidden = NO;
  365. }
  366. else {
  367. cell.videoIndicatorView.videoIcon.hidden = NO;
  368. cell.videoIndicatorView.slomoIcon.hidden = YES;
  369. }
  370. } else {
  371. cell.videoIndicatorView.hidden = YES;
  372. }
  373. // Selection state
  374. if ([self.imagePickerController.selectedAssets containsObject:asset]) {
  375. [cell setSelected:YES];
  376. [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
  377. }
  378. return cell;
  379. }
  380. - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  381. {
  382. if (kind == UICollectionElementKindSectionFooter) {
  383. UICollectionReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
  384. withReuseIdentifier:@"FooterView"
  385. forIndexPath:indexPath];
  386. // Number of assets
  387. UILabel *label = (UILabel *)[footerView viewWithTag:1];
  388. NSBundle *bundle = self.imagePickerController.assetBundle;
  389. NSUInteger numberOfPhotos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeImage];
  390. NSUInteger numberOfVideos = [self.fetchResult countOfAssetsWithMediaType:PHAssetMediaTypeVideo];
  391. switch (self.imagePickerController.mediaType) {
  392. case QBImagePickerMediaTypeAny:
  393. {
  394. NSString *format;
  395. if (numberOfPhotos == 1) {
  396. if (numberOfVideos == 1) {
  397. format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-video", @"QBImagePicker", bundle, nil);
  398. } else {
  399. format = NSLocalizedStringFromTableInBundle(@"assets.footer.photo-and-videos", @"QBImagePicker", bundle, nil);
  400. }
  401. } else if (numberOfVideos == 1) {
  402. format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-video", @"QBImagePicker", bundle, nil);
  403. } else {
  404. format = NSLocalizedStringFromTableInBundle(@"assets.footer.photos-and-videos", @"QBImagePicker", bundle, nil);
  405. }
  406. label.text = [NSString stringWithFormat:format, numberOfPhotos, numberOfVideos];
  407. }
  408. break;
  409. case QBImagePickerMediaTypeImage:
  410. {
  411. NSString *key = (numberOfPhotos == 1) ? @"assets.footer.photo" : @"assets.footer.photos";
  412. NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);
  413. label.text = [NSString stringWithFormat:format, numberOfPhotos];
  414. }
  415. break;
  416. case QBImagePickerMediaTypeVideo:
  417. {
  418. NSString *key = (numberOfVideos == 1) ? @"assets.footer.video" : @"assets.footer.videos";
  419. NSString *format = NSLocalizedStringFromTableInBundle(key, @"QBImagePicker", bundle, nil);
  420. label.text = [NSString stringWithFormat:format, numberOfVideos];
  421. }
  422. break;
  423. }
  424. return footerView;
  425. }
  426. return nil;
  427. }
  428. #pragma mark - UICollectionViewDelegate
  429. - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
  430. {
  431. if ([self.imagePickerController.delegate respondsToSelector:@selector(qb_imagePickerController:shouldSelectAsset:)]) {
  432. PHAsset *asset = self.fetchResult[indexPath.item];
  433. return [self.imagePickerController.delegate qb_imagePickerController:self.imagePickerController shouldSelectAsset:asset];
  434. }
  435. if ([self isAutoDeselectEnabled]) {
  436. return YES;
  437. }
  438. return ![self isMaximumSelectionLimitReached];
  439. }
  440. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  441. {
  442. QBImagePickerController *imagePickerController = self.imagePickerController;
  443. NSMutableOrderedSet *selectedAssets = imagePickerController.selectedAssets;
  444. PHAsset *asset = self.fetchResult[indexPath.item];
  445. if (imagePickerController.allowsMultipleSelection) {
  446. if ([self isAutoDeselectEnabled] && selectedAssets.count > 0) {
  447. // Remove previous selected asset from set
  448. [selectedAssets removeObjectAtIndex:0];
  449. // Deselect previous selected asset
  450. if (self.lastSelectedItemIndexPath) {
  451. [collectionView deselectItemAtIndexPath:self.lastSelectedItemIndexPath animated:NO];
  452. }
  453. }
  454. // Add asset to set
  455. [selectedAssets addObject:asset];
  456. self.lastSelectedItemIndexPath = indexPath;
  457. [self updateDoneButtonState];
  458. if (imagePickerController.showsNumberOfSelectedAssets) {
  459. [self updateSelectionInfo];
  460. if (selectedAssets.count == 1) {
  461. // Show toolbar
  462. [self.navigationController setToolbarHidden:NO animated:YES];
  463. }
  464. }
  465. } else {
  466. if ([imagePickerController.delegate respondsToSelector:@selector(qb_imagePickerController:didFinishPickingAssets:)]) {
  467. [imagePickerController.delegate qb_imagePickerController:imagePickerController didFinishPickingAssets:@[asset]];
  468. }
  469. }
  470. if ([imagePickerController.delegate respondsToSelector:@selector(qb_imagePickerController:didSelectAsset:)]) {
  471. [imagePickerController.delegate qb_imagePickerController:imagePickerController didSelectAsset:asset];
  472. }
  473. }
  474. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  475. {
  476. if (!self.imagePickerController.allowsMultipleSelection) {
  477. return;
  478. }
  479. QBImagePickerController *imagePickerController = self.imagePickerController;
  480. NSMutableOrderedSet *selectedAssets = imagePickerController.selectedAssets;
  481. PHAsset *asset = self.fetchResult[indexPath.item];
  482. // Remove asset from set
  483. [selectedAssets removeObject:asset];
  484. self.lastSelectedItemIndexPath = nil;
  485. [self updateDoneButtonState];
  486. if (imagePickerController.showsNumberOfSelectedAssets) {
  487. [self updateSelectionInfo];
  488. if (selectedAssets.count == 0) {
  489. // Hide toolbar
  490. [self.navigationController setToolbarHidden:YES animated:YES];
  491. }
  492. }
  493. if ([imagePickerController.delegate respondsToSelector:@selector(qb_imagePickerController:didDeselectAsset:)]) {
  494. [imagePickerController.delegate qb_imagePickerController:imagePickerController didDeselectAsset:asset];
  495. }
  496. }
  497. #pragma mark - UICollectionViewDelegateFlowLayout
  498. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  499. {
  500. NSUInteger numberOfColumns;
  501. if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
  502. numberOfColumns = self.imagePickerController.numberOfColumnsInPortrait;
  503. } else {
  504. numberOfColumns = self.imagePickerController.numberOfColumnsInLandscape;
  505. }
  506. CGFloat width = (CGRectGetWidth(self.view.frame) - 2.0 * (numberOfColumns - 1)) / numberOfColumns;
  507. return CGSizeMake(width, width);
  508. }
  509. @end