CTAssetsGridViewController.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. /*
  2. MIT License (MIT)
  3. Copyright (c) 2015 Clement CN Tsang
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. #import "CTAssetsPickerDefines.h"
  21. #import "CTAssetsPickerController.h"
  22. #import "CTAssetsPickerController+Internal.h"
  23. #import "CTAssetsGridViewController.h"
  24. #import "CTAssetsGridView.h"
  25. #import "CTAssetsGridViewLayout.h"
  26. #import "CTAssetsGridViewCell.h"
  27. #import "CTAssetsGridViewFooter.h"
  28. #import "CTAssetsPickerNoAssetsView.h"
  29. #import "CTAssetsPageViewController.h"
  30. #import "CTAssetsPageViewController+Internal.h"
  31. #import "CTAssetsViewControllerTransition.h"
  32. #import "UICollectionView+CTAssetsPickerController.h"
  33. #import "NSIndexSet+CTAssetsPickerController.h"
  34. #import "NSBundle+CTAssetsPickerController.h"
  35. #import "PHImageManager+CTAssetsPickerController.h"
  36. NSString * const CTAssetsGridViewCellIdentifier = @"CTAssetsGridViewCellIdentifier";
  37. NSString * const CTAssetsGridViewFooterIdentifier = @"CTAssetsGridViewFooterIdentifier";
  38. @interface CTAssetsGridViewController ()
  39. <PHPhotoLibraryChangeObserver>
  40. @property (nonatomic, weak) CTAssetsPickerController *picker;
  41. @property (nonatomic, strong) PHFetchResult *fetchResult;
  42. @property (nonatomic, strong) PHCachingImageManager *imageManager;
  43. @property (nonatomic, assign) CGRect previousPreheatRect;
  44. @property (nonatomic, assign) CGRect previousBounds;
  45. @property (nonatomic, strong) CTAssetsGridViewFooter *footer;
  46. @property (nonatomic, strong) CTAssetsPickerNoAssetsView *noAssetsView;
  47. @property (nonatomic, assign) BOOL didLayoutSubviews;
  48. @end
  49. @implementation CTAssetsGridViewController
  50. - (instancetype)init
  51. {
  52. CTAssetsGridViewLayout *layout = [CTAssetsGridViewLayout new];
  53. if (self = [super initWithCollectionViewLayout:layout])
  54. {
  55. _imageManager = [PHCachingImageManager new];
  56. self.extendedLayoutIncludesOpaqueBars = YES;
  57. self.collectionView.allowsMultipleSelection = YES;
  58. [self.collectionView registerClass:CTAssetsGridViewCell.class
  59. forCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier];
  60. [self.collectionView registerClass:CTAssetsGridViewFooter.class
  61. forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
  62. withReuseIdentifier:CTAssetsGridViewFooterIdentifier];
  63. [self addNotificationObserver];
  64. }
  65. return self;
  66. }
  67. - (void)viewDidLoad
  68. {
  69. [super viewDidLoad];
  70. [self setupViews];
  71. [self registerChangeObserver];
  72. [self addGestureRecognizer];
  73. [self addNotificationObserver];
  74. [self resetCachedAssetImages];
  75. }
  76. - (void)viewWillAppear:(BOOL)animated
  77. {
  78. [super viewWillAppear:animated];
  79. [self setupButtons];
  80. [self setupAssets];
  81. [self updateTitle:self.picker.selectedAssets];
  82. [self updateButton:self.picker.selectedAssets];
  83. }
  84. - (void)viewDidAppear:(BOOL)animated
  85. {
  86. [super viewDidAppear:animated];
  87. [self updateCachedAssetImages];
  88. }
  89. - (void)viewWillLayoutSubviews
  90. {
  91. [super viewWillLayoutSubviews];
  92. if (!CGRectEqualToRect(self.view.bounds, self.previousBounds))
  93. {
  94. [self updateCollectionViewLayout];
  95. self.previousBounds = self.view.bounds;
  96. }
  97. }
  98. - (void)viewDidLayoutSubviews
  99. {
  100. [super viewDidLayoutSubviews];
  101. if (!self.didLayoutSubviews && self.fetchResult.count > 0)
  102. {
  103. [self scrollToBottomIfNeeded];
  104. self.didLayoutSubviews = YES;
  105. }
  106. }
  107. - (void)dealloc
  108. {
  109. [self unregisterChangeObserver];
  110. [self removeNotificationObserver];
  111. }
  112. #pragma mark - Accessors
  113. - (CTAssetsPickerController *)picker
  114. {
  115. return (CTAssetsPickerController *)self.splitViewController.parentViewController;
  116. }
  117. - (PHAsset *)assetAtIndexPath:(NSIndexPath *)indexPath
  118. {
  119. return (self.fetchResult.count > 0) ? self.fetchResult[indexPath.item] : nil;
  120. }
  121. #pragma mark - Setup
  122. - (void)setupViews
  123. {
  124. self.collectionView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
  125. CTAssetsGridView *gridView = [CTAssetsGridView new];
  126. [self.view insertSubview:gridView atIndex:0];
  127. [self.view setNeedsUpdateConstraints];
  128. }
  129. - (void)setupButtons
  130. {
  131. if (self.navigationItem.rightBarButtonItem == nil)
  132. {
  133. NSString *title = (self.picker.doneButtonTitle) ?
  134. self.picker.doneButtonTitle : CTAssetsPickerLocalizedString(@"Done", nil);
  135. self.navigationItem.rightBarButtonItem =
  136. [[UIBarButtonItem alloc] initWithTitle:title
  137. style:UIBarButtonItemStyleDone
  138. target:self.picker
  139. action:@selector(finishPickingAssets:)];
  140. }
  141. }
  142. - (void)setupAssets
  143. {
  144. PHFetchResult *fetchResult =
  145. [PHAsset fetchAssetsInAssetCollection:self.assetCollection
  146. options:self.picker.assetsFetchOptions];
  147. self.fetchResult = fetchResult;
  148. [self reloadData];
  149. }
  150. #pragma mark - Collection view layout
  151. - (void)updateCollectionViewLayout
  152. {
  153. UITraitCollection *trait = self.traitCollection;
  154. CGSize contentSize = self.view.bounds.size;
  155. UICollectionViewLayout *layout;
  156. NSArray *attributes = [self.collectionView.collectionViewLayout layoutAttributesForElementsInRect:self.collectionView.bounds];
  157. UICollectionViewLayoutAttributes *attr = (UICollectionViewLayoutAttributes*)attributes.firstObject;
  158. // new content size should be at least of first item size, else ignoring
  159. if (contentSize.width < attr.size.width || contentSize.height < attr.size.height) {
  160. return;
  161. }
  162. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:collectionViewLayoutForContentSize:traitCollection:)]) {
  163. layout = [self.picker.delegate assetsPickerController:self.picker collectionViewLayoutForContentSize:contentSize traitCollection:trait];
  164. } else {
  165. layout = [[CTAssetsGridViewLayout alloc] initWithContentSize:contentSize traitCollection:trait];
  166. }
  167. __weak CTAssetsGridViewController *weakSelf = self;
  168. [self.collectionView setCollectionViewLayout:layout animated:NO completion:^(BOOL finished){
  169. [weakSelf.collectionView reloadItemsAtIndexPaths:[weakSelf.collectionView indexPathsForVisibleItems]];
  170. }];
  171. }
  172. #pragma mark - Scroll to bottom
  173. - (void)scrollToBottomIfNeeded
  174. {
  175. BOOL shouldScrollToBottom;
  176. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldScrollToBottomForAssetCollection:)])
  177. shouldScrollToBottom = [self.picker.delegate assetsPickerController:self.picker shouldScrollToBottomForAssetCollection:self.assetCollection];
  178. else
  179. shouldScrollToBottom = YES;
  180. if (shouldScrollToBottom)
  181. {
  182. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.fetchResult.count-1 inSection:0];
  183. [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
  184. }
  185. }
  186. #pragma mark - Notifications
  187. - (void)addNotificationObserver
  188. {
  189. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  190. [center addObserver:self
  191. selector:@selector(assetsPickerSelectedAssetsDidChange:)
  192. name:CTAssetsPickerSelectedAssetsDidChangeNotification
  193. object:nil];
  194. [center addObserver:self
  195. selector:@selector(assetsPickerDidSelectAsset:)
  196. name:CTAssetsPickerDidSelectAssetNotification
  197. object:nil];
  198. [center addObserver:self
  199. selector:@selector(assetsPickerDidDeselectAsset:)
  200. name:CTAssetsPickerDidDeselectAssetNotification
  201. object:nil];
  202. }
  203. - (void)removeNotificationObserver
  204. {
  205. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  206. [center removeObserver:self name:CTAssetsPickerSelectedAssetsDidChangeNotification object:nil];
  207. [center removeObserver:self name:CTAssetsPickerDidSelectAssetNotification object:nil];
  208. [center removeObserver:self name:CTAssetsPickerDidDeselectAssetNotification object:nil];
  209. }
  210. #pragma mark - Photo library change observer
  211. - (void)registerChangeObserver
  212. {
  213. [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
  214. }
  215. - (void)unregisterChangeObserver
  216. {
  217. [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
  218. }
  219. #pragma mark - Photo library changed
  220. - (void)photoLibraryDidChange:(PHChange *)changeInstance
  221. {
  222. // Call might come on any background queue. Re-dispatch to the main queue to handle it.
  223. dispatch_async(dispatch_get_main_queue(), ^{
  224. PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:self.fetchResult];
  225. if (changeDetails)
  226. {
  227. self.fetchResult = changeDetails.fetchResultAfterChanges;
  228. UICollectionView *collectionView = self.collectionView;
  229. if (!changeDetails.hasIncrementalChanges || changeDetails.hasMoves)
  230. {
  231. [collectionView reloadData];
  232. [self resetCachedAssetImages];
  233. }
  234. else
  235. {
  236. NSArray *removedPaths;
  237. NSArray *insertedPaths;
  238. NSArray *changedPaths;
  239. NSIndexSet *removedIndexes = changeDetails.removedIndexes;
  240. removedPaths = [removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
  241. NSIndexSet *insertedIndexes = changeDetails.insertedIndexes;
  242. insertedPaths = [insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
  243. NSIndexSet *changedIndexes = changeDetails.changedIndexes;
  244. changedPaths = [changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
  245. BOOL shouldReload = NO;
  246. if (changedPaths != nil && removedPaths != nil)
  247. {
  248. for (NSIndexPath *changedPath in changedPaths)
  249. {
  250. if ([removedPaths containsObject:changedPath])
  251. {
  252. shouldReload = YES;
  253. break;
  254. }
  255. }
  256. }
  257. if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.fetchResult.count)
  258. {
  259. shouldReload = YES;
  260. }
  261. if (shouldReload)
  262. {
  263. [collectionView reloadData];
  264. }
  265. else
  266. {
  267. // if we have incremental diffs, tell the collection view to animate insertions and deletions
  268. [collectionView performBatchUpdates:^{
  269. if (removedPaths.count)
  270. {
  271. [collectionView deleteItemsAtIndexPaths:[removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
  272. }
  273. if (insertedPaths.count)
  274. {
  275. [collectionView insertItemsAtIndexPaths:[insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
  276. }
  277. if (changedPaths.count)
  278. {
  279. [collectionView reloadItemsAtIndexPaths:[changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0] ];
  280. }
  281. } completion:^(BOOL finished){
  282. if (finished)
  283. [self resetCachedAssetImages];
  284. }];
  285. }
  286. }
  287. [self.footer bind:self.fetchResult];
  288. if (self.fetchResult.count == 0)
  289. [self showNoAssets];
  290. else
  291. [self hideNoAssets];
  292. }
  293. if ([self.delegate respondsToSelector:@selector(assetsGridViewController:photoLibraryDidChangeForAssetCollection:)])
  294. [self.delegate assetsGridViewController:self photoLibraryDidChangeForAssetCollection:self.assetCollection];
  295. });
  296. }
  297. #pragma mark - Selected assets changed
  298. - (void)assetsPickerSelectedAssetsDidChange:(NSNotification *)notification
  299. {
  300. NSArray *selectedAssets = (NSArray *)notification.object;
  301. [self updateTitle:selectedAssets];
  302. [self updateButton:selectedAssets];
  303. }
  304. - (void)updateTitle:(NSArray *)selectedAssets
  305. {
  306. if (selectedAssets.count > 0)
  307. self.title = self.picker.selectedAssetsString;
  308. else
  309. self.title = self.assetCollection.localizedTitle;
  310. }
  311. - (void)updateButton:(NSArray *)selectedAssets
  312. {
  313. if (self.picker.alwaysEnableDoneButton)
  314. self.navigationItem.rightBarButtonItem.enabled = YES;
  315. else
  316. self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
  317. }
  318. #pragma mark - Did de/select asset notifications
  319. - (void)assetsPickerDidSelectAsset:(NSNotification *)notification
  320. {
  321. PHAsset *asset = (PHAsset *)notification.object;
  322. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
  323. [self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
  324. [self updateSelectionOrderLabels];
  325. }
  326. - (void)assetsPickerDidDeselectAsset:(NSNotification *)notification
  327. {
  328. PHAsset *asset = (PHAsset *)notification.object;
  329. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
  330. [self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
  331. [self updateSelectionOrderLabels];
  332. }
  333. #pragma mark - Update Selection Order Labels
  334. - (void)updateSelectionOrderLabels
  335. {
  336. for (NSIndexPath *indexPath in [self.collectionView indexPathsForSelectedItems])
  337. {
  338. PHAsset *asset = [self assetAtIndexPath:indexPath];
  339. CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
  340. cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
  341. }
  342. }
  343. #pragma mark - Gesture recognizer
  344. - (void)addGestureRecognizer
  345. {
  346. UILongPressGestureRecognizer *longPress =
  347. [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pushPageViewController:)];
  348. [self.collectionView addGestureRecognizer:longPress];
  349. }
  350. #pragma mark - Push assets page view controller
  351. - (void)pushPageViewController:(UILongPressGestureRecognizer *)longPress
  352. {
  353. if (longPress.state == UIGestureRecognizerStateBegan)
  354. {
  355. CGPoint point = [longPress locationInView:self.collectionView];
  356. NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
  357. CTAssetsPageViewController *vc = [[CTAssetsPageViewController alloc] initWithFetchResult:self.fetchResult];
  358. vc.allowsSelection = YES;
  359. vc.pageIndex = indexPath.item;
  360. [self.navigationController pushViewController:vc animated:YES];
  361. }
  362. }
  363. #pragma mark - Reload data
  364. - (void)reloadData
  365. {
  366. if (self.fetchResult.count > 0)
  367. {
  368. [self hideNoAssets];
  369. [self.collectionView reloadData];
  370. }
  371. else
  372. {
  373. [self showNoAssets];
  374. }
  375. }
  376. #pragma mark - Asset images caching
  377. - (void)resetCachedAssetImages
  378. {
  379. [self.imageManager stopCachingImagesForAllAssets];
  380. self.previousPreheatRect = CGRectZero;
  381. }
  382. - (void)updateCachedAssetImages
  383. {
  384. BOOL isViewVisible = [self isViewLoaded] && self.view.window != nil;
  385. if (!isViewVisible)
  386. return;
  387. // The preheat window is twice the height of the visible rect
  388. CGRect preheatRect = self.collectionView.bounds;
  389. preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
  390. // If scrolled by a "reasonable" amount...
  391. CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
  392. if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f)
  393. {
  394. // Compute the assets to start caching and to stop caching.
  395. NSMutableArray *addedIndexPaths = [NSMutableArray array];
  396. NSMutableArray *removedIndexPaths = [NSMutableArray array];
  397. [self computeDifferenceBetweenRect:self.previousPreheatRect
  398. andRect:preheatRect
  399. removedHandler:^(CGRect removedRect) {
  400. NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:removedRect];
  401. [removedIndexPaths addObjectsFromArray:indexPaths];
  402. } addedHandler:^(CGRect addedRect) {
  403. NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:addedRect];
  404. [addedIndexPaths addObjectsFromArray:indexPaths];
  405. }];
  406. [self startCachingThumbnailsForIndexPaths:addedIndexPaths];
  407. [self stopCachingThumbnailsForIndexPaths:removedIndexPaths];
  408. self.previousPreheatRect = preheatRect;
  409. }
  410. }
  411. - (void)startCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
  412. {
  413. for (NSIndexPath *indexPath in indexPaths)
  414. {
  415. PHAsset *asset = [self assetAtIndexPath:indexPath];
  416. if (!asset) break;
  417. UICollectionViewLayoutAttributes *attributes =
  418. [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  419. CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
  420. [self.imageManager startCachingImagesForAssets:@[asset]
  421. targetSize:targetSize
  422. contentMode:PHImageContentModeAspectFill
  423. options:self.picker.thumbnailRequestOptions];
  424. }
  425. }
  426. - (void)stopCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
  427. {
  428. for (NSIndexPath *indexPath in indexPaths)
  429. {
  430. PHAsset *asset = [self assetAtIndexPath:indexPath];
  431. if (!asset) break;
  432. UICollectionViewLayoutAttributes *attributes =
  433. [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  434. CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
  435. [self.imageManager stopCachingImagesForAssets:@[asset]
  436. targetSize:targetSize
  437. contentMode:PHImageContentModeAspectFill
  438. options:self.picker.thumbnailRequestOptions];
  439. }
  440. }
  441. - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
  442. {
  443. if (CGRectIntersectsRect(newRect, oldRect)) {
  444. CGFloat oldMaxY = CGRectGetMaxY(oldRect);
  445. CGFloat oldMinY = CGRectGetMinY(oldRect);
  446. CGFloat newMaxY = CGRectGetMaxY(newRect);
  447. CGFloat newMinY = CGRectGetMinY(newRect);
  448. if (newMaxY > oldMaxY) {
  449. CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
  450. addedHandler(rectToAdd);
  451. }
  452. if (oldMinY > newMinY) {
  453. CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
  454. addedHandler(rectToAdd);
  455. }
  456. if (newMaxY < oldMaxY) {
  457. CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
  458. removedHandler(rectToRemove);
  459. }
  460. if (oldMinY < newMinY) {
  461. CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
  462. removedHandler(rectToRemove);
  463. }
  464. } else {
  465. addedHandler(newRect);
  466. removedHandler(oldRect);
  467. }
  468. }
  469. #pragma mark - Scroll view delegate
  470. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  471. {
  472. [self updateCachedAssetImages];
  473. }
  474. #pragma mark - No assets
  475. - (void)showNoAssets
  476. {
  477. CTAssetsPickerNoAssetsView *view = [CTAssetsPickerNoAssetsView new];
  478. [self.view addSubview:view];
  479. [view setNeedsUpdateConstraints];
  480. [view updateConstraintsIfNeeded];
  481. self.noAssetsView = view;
  482. }
  483. - (void)hideNoAssets
  484. {
  485. if (self.noAssetsView)
  486. {
  487. [self.noAssetsView removeFromSuperview];
  488. self.noAssetsView = nil;
  489. }
  490. }
  491. #pragma mark - Collection view data source
  492. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
  493. {
  494. return 1;
  495. }
  496. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  497. {
  498. return self.fetchResult.count;
  499. }
  500. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  501. {
  502. CTAssetsGridViewCell *cell =
  503. [collectionView dequeueReusableCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier
  504. forIndexPath:indexPath];
  505. PHAsset *asset = [self assetAtIndexPath:indexPath];
  506. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)])
  507. cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
  508. else
  509. cell.enabled = YES;
  510. cell.showsSelectionIndex = self.picker.showsSelectionIndex;
  511. // XXX
  512. // Setting `selected` property blocks further deselection.
  513. // Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
  514. if ([self.picker.selectedAssets containsObject:asset])
  515. {
  516. cell.selected = YES;
  517. cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
  518. [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
  519. }
  520. [cell bind:asset];
  521. UICollectionViewLayoutAttributes *attributes =
  522. [collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  523. CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
  524. [self requestThumbnailForCell:cell targetSize:targetSize asset:asset];
  525. return cell;
  526. }
  527. - (void)requestThumbnailForCell:(CTAssetsGridViewCell *)cell targetSize:(CGSize)targetSize asset:(PHAsset *)asset
  528. {
  529. NSInteger tag = cell.tag + 1;
  530. cell.tag = tag;
  531. [self.imageManager ctassetsPickerRequestImageForAsset:asset
  532. targetSize:targetSize
  533. contentMode:PHImageContentModeAspectFill
  534. options:self.picker.thumbnailRequestOptions
  535. resultHandler:^(UIImage *image, NSDictionary *info){
  536. // Only update the image if the cell tag hasn't changed. Otherwise, the cell has been re-used.
  537. if (cell.tag == tag)
  538. [(CTAssetThumbnailView *)cell.backgroundView bind:image asset:asset];
  539. }];
  540. }
  541. - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
  542. {
  543. CTAssetsGridViewFooter *footer =
  544. [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
  545. withReuseIdentifier:CTAssetsGridViewFooterIdentifier
  546. forIndexPath:indexPath];
  547. [footer bind:self.fetchResult];
  548. self.footer = footer;
  549. return footer;
  550. }
  551. #pragma mark - Collection view delegate
  552. - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
  553. {
  554. PHAsset *asset = [self assetAtIndexPath:indexPath];
  555. CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
  556. if (!cell.isEnabled)
  557. return NO;
  558. else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)])
  559. return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
  560. else
  561. return YES;
  562. }
  563. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  564. {
  565. PHAsset *asset = [self assetAtIndexPath:indexPath];
  566. [self.picker selectAsset:asset];
  567. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)])
  568. [self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
  569. }
  570. - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  571. {
  572. PHAsset *asset = [self assetAtIndexPath:indexPath];
  573. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)])
  574. return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
  575. else
  576. return YES;
  577. }
  578. - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
  579. {
  580. PHAsset *asset = [self assetAtIndexPath:indexPath];
  581. [self.picker deselectAsset:asset];
  582. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)])
  583. [self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
  584. }
  585. - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
  586. {
  587. PHAsset *asset = [self assetAtIndexPath:indexPath];
  588. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)])
  589. return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
  590. else
  591. return YES;
  592. }
  593. - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
  594. {
  595. PHAsset *asset = [self assetAtIndexPath:indexPath];
  596. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)])
  597. [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
  598. }
  599. - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
  600. {
  601. PHAsset *asset = [self assetAtIndexPath:indexPath];
  602. if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)])
  603. [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
  604. }
  605. @end