|
@@ -0,0 +1,802 @@
|
|
|
+/*
|
|
|
+
|
|
|
+ MIT License (MIT)
|
|
|
+
|
|
|
+ Copyright (c) 2015 Clement CN Tsang
|
|
|
+
|
|
|
+ Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+ of this software and associated documentation files (the "Software"), to deal
|
|
|
+ in the Software without restriction, including without limitation the rights
|
|
|
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+ copies of the Software, and to permit persons to whom the Software is
|
|
|
+ furnished to do so, subject to the following conditions:
|
|
|
+
|
|
|
+ The above copyright notice and this permission notice shall be included in
|
|
|
+ all copies or substantial portions of the Software.
|
|
|
+
|
|
|
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
+ THE SOFTWARE.
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+#import "CTAssetsPickerDefines.h"
|
|
|
+#import "CTAssetsPickerController.h"
|
|
|
+#import "CTAssetsPickerController+Internal.h"
|
|
|
+#import "CTAssetsGridViewController.h"
|
|
|
+#import "CTAssetsGridView.h"
|
|
|
+#import "CTAssetsGridViewLayout.h"
|
|
|
+#import "CTAssetsGridViewCell.h"
|
|
|
+#import "CTAssetsGridViewFooter.h"
|
|
|
+#import "CTAssetsPickerNoAssetsView.h"
|
|
|
+#import "CTAssetsPageViewController.h"
|
|
|
+#import "CTAssetsPageViewController+Internal.h"
|
|
|
+#import "CTAssetsViewControllerTransition.h"
|
|
|
+#import "UICollectionView+CTAssetsPickerController.h"
|
|
|
+#import "NSIndexSet+CTAssetsPickerController.h"
|
|
|
+#import "NSBundle+CTAssetsPickerController.h"
|
|
|
+#import "PHImageManager+CTAssetsPickerController.h"
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+NSString * const CTAssetsGridViewCellIdentifier = @"CTAssetsGridViewCellIdentifier";
|
|
|
+NSString * const CTAssetsGridViewFooterIdentifier = @"CTAssetsGridViewFooterIdentifier";
|
|
|
+
|
|
|
+
|
|
|
+@interface CTAssetsGridViewController ()
|
|
|
+<PHPhotoLibraryChangeObserver>
|
|
|
+
|
|
|
+@property (nonatomic, weak) CTAssetsPickerController *picker;
|
|
|
+@property (nonatomic, strong) PHFetchResult *fetchResult;
|
|
|
+@property (nonatomic, strong) PHCachingImageManager *imageManager;
|
|
|
+
|
|
|
+@property (nonatomic, assign) CGRect previousPreheatRect;
|
|
|
+@property (nonatomic, assign) CGRect previousBounds;
|
|
|
+
|
|
|
+@property (nonatomic, strong) CTAssetsGridViewFooter *footer;
|
|
|
+@property (nonatomic, strong) CTAssetsPickerNoAssetsView *noAssetsView;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL didLayoutSubviews;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+@implementation CTAssetsGridViewController
|
|
|
+
|
|
|
+
|
|
|
+- (instancetype)init
|
|
|
+{
|
|
|
+ CTAssetsGridViewLayout *layout = [CTAssetsGridViewLayout new];
|
|
|
+
|
|
|
+ if (self = [super initWithCollectionViewLayout:layout])
|
|
|
+ {
|
|
|
+ _imageManager = [PHCachingImageManager new];
|
|
|
+
|
|
|
+ self.extendedLayoutIncludesOpaqueBars = YES;
|
|
|
+
|
|
|
+ self.collectionView.allowsMultipleSelection = YES;
|
|
|
+
|
|
|
+ [self.collectionView registerClass:CTAssetsGridViewCell.class
|
|
|
+ forCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier];
|
|
|
+
|
|
|
+ [self.collectionView registerClass:CTAssetsGridViewFooter.class
|
|
|
+ forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
|
|
|
+ withReuseIdentifier:CTAssetsGridViewFooterIdentifier];
|
|
|
+
|
|
|
+ [self addNotificationObserver];
|
|
|
+ }
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewDidLoad
|
|
|
+{
|
|
|
+ [super viewDidLoad];
|
|
|
+ [self setupViews];
|
|
|
+ [self registerChangeObserver];
|
|
|
+ [self addGestureRecognizer];
|
|
|
+ [self addNotificationObserver];
|
|
|
+ [self resetCachedAssetImages];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillAppear:(BOOL)animated
|
|
|
+{
|
|
|
+ [super viewWillAppear:animated];
|
|
|
+ [self setupButtons];
|
|
|
+ [self setupAssets];
|
|
|
+ [self updateTitle:self.picker.selectedAssets];
|
|
|
+ [self updateButton:self.picker.selectedAssets];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewDidAppear:(BOOL)animated
|
|
|
+{
|
|
|
+ [super viewDidAppear:animated];
|
|
|
+ [self updateCachedAssetImages];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillLayoutSubviews
|
|
|
+{
|
|
|
+ [super viewWillLayoutSubviews];
|
|
|
+
|
|
|
+ if (!CGRectEqualToRect(self.view.bounds, self.previousBounds))
|
|
|
+ {
|
|
|
+ [self updateCollectionViewLayout];
|
|
|
+ self.previousBounds = self.view.bounds;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewDidLayoutSubviews
|
|
|
+{
|
|
|
+ [super viewDidLayoutSubviews];
|
|
|
+
|
|
|
+ if (!self.didLayoutSubviews && self.fetchResult.count > 0)
|
|
|
+ {
|
|
|
+ [self scrollToBottomIfNeeded];
|
|
|
+ self.didLayoutSubviews = YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dealloc
|
|
|
+{
|
|
|
+ [self unregisterChangeObserver];
|
|
|
+ [self removeNotificationObserver];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Accessors
|
|
|
+
|
|
|
+- (CTAssetsPickerController *)picker
|
|
|
+{
|
|
|
+ return (CTAssetsPickerController *)self.splitViewController.parentViewController;
|
|
|
+}
|
|
|
+
|
|
|
+- (PHAsset *)assetAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ return (self.fetchResult.count > 0) ? self.fetchResult[indexPath.item] : nil;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Setup
|
|
|
+
|
|
|
+- (void)setupViews
|
|
|
+{
|
|
|
+ self.collectionView.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
|
|
|
+ CTAssetsGridView *gridView = [CTAssetsGridView new];
|
|
|
+ [self.view insertSubview:gridView atIndex:0];
|
|
|
+ [self.view setNeedsUpdateConstraints];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setupButtons
|
|
|
+{
|
|
|
+ if (self.navigationItem.rightBarButtonItem == nil)
|
|
|
+ {
|
|
|
+ NSString *title = (self.picker.doneButtonTitle) ?
|
|
|
+ self.picker.doneButtonTitle : CTAssetsPickerLocalizedString(@"Done", nil);
|
|
|
+
|
|
|
+ self.navigationItem.rightBarButtonItem =
|
|
|
+ [[UIBarButtonItem alloc] initWithTitle:title
|
|
|
+ style:UIBarButtonItemStyleDone
|
|
|
+ target:self.picker
|
|
|
+ action:@selector(finishPickingAssets:)];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setupAssets
|
|
|
+{
|
|
|
+ PHFetchResult *fetchResult =
|
|
|
+ [PHAsset fetchAssetsInAssetCollection:self.assetCollection
|
|
|
+ options:self.picker.assetsFetchOptions];
|
|
|
+
|
|
|
+ self.fetchResult = fetchResult;
|
|
|
+ [self reloadData];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Collection view layout
|
|
|
+
|
|
|
+- (void)updateCollectionViewLayout
|
|
|
+{
|
|
|
+ UITraitCollection *trait = self.traitCollection;
|
|
|
+ CGSize contentSize = self.view.bounds.size;
|
|
|
+ UICollectionViewLayout *layout;
|
|
|
+
|
|
|
+ NSArray *attributes = [self.collectionView.collectionViewLayout layoutAttributesForElementsInRect:self.collectionView.bounds];
|
|
|
+ UICollectionViewLayoutAttributes *attr = (UICollectionViewLayoutAttributes*)attributes.firstObject;
|
|
|
+ // new content size should be at least of first item size, else ignoring
|
|
|
+ if (contentSize.width < attr.size.width || contentSize.height < attr.size.height) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:collectionViewLayoutForContentSize:traitCollection:)]) {
|
|
|
+ layout = [self.picker.delegate assetsPickerController:self.picker collectionViewLayoutForContentSize:contentSize traitCollection:trait];
|
|
|
+ } else {
|
|
|
+ layout = [[CTAssetsGridViewLayout alloc] initWithContentSize:contentSize traitCollection:trait];
|
|
|
+ }
|
|
|
+
|
|
|
+ __weak CTAssetsGridViewController *weakSelf = self;
|
|
|
+
|
|
|
+ [self.collectionView setCollectionViewLayout:layout animated:NO completion:^(BOOL finished){
|
|
|
+ [weakSelf.collectionView reloadItemsAtIndexPaths:[weakSelf.collectionView indexPathsForVisibleItems]];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Scroll to bottom
|
|
|
+
|
|
|
+- (void)scrollToBottomIfNeeded
|
|
|
+{
|
|
|
+ BOOL shouldScrollToBottom;
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldScrollToBottomForAssetCollection:)])
|
|
|
+ shouldScrollToBottom = [self.picker.delegate assetsPickerController:self.picker shouldScrollToBottomForAssetCollection:self.assetCollection];
|
|
|
+ else
|
|
|
+ shouldScrollToBottom = YES;
|
|
|
+
|
|
|
+ if (shouldScrollToBottom)
|
|
|
+ {
|
|
|
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.fetchResult.count-1 inSection:0];
|
|
|
+ [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Notifications
|
|
|
+
|
|
|
+- (void)addNotificationObserver
|
|
|
+{
|
|
|
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
|
|
+
|
|
|
+ [center addObserver:self
|
|
|
+ selector:@selector(assetsPickerSelectedAssetsDidChange:)
|
|
|
+ name:CTAssetsPickerSelectedAssetsDidChangeNotification
|
|
|
+ object:nil];
|
|
|
+
|
|
|
+ [center addObserver:self
|
|
|
+ selector:@selector(assetsPickerDidSelectAsset:)
|
|
|
+ name:CTAssetsPickerDidSelectAssetNotification
|
|
|
+ object:nil];
|
|
|
+
|
|
|
+ [center addObserver:self
|
|
|
+ selector:@selector(assetsPickerDidDeselectAsset:)
|
|
|
+ name:CTAssetsPickerDidDeselectAssetNotification
|
|
|
+ object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)removeNotificationObserver
|
|
|
+{
|
|
|
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
|
|
+
|
|
|
+ [center removeObserver:self name:CTAssetsPickerSelectedAssetsDidChangeNotification object:nil];
|
|
|
+ [center removeObserver:self name:CTAssetsPickerDidSelectAssetNotification object:nil];
|
|
|
+ [center removeObserver:self name:CTAssetsPickerDidDeselectAssetNotification object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Photo library change observer
|
|
|
+
|
|
|
+- (void)registerChangeObserver
|
|
|
+{
|
|
|
+ [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)unregisterChangeObserver
|
|
|
+{
|
|
|
+ [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Photo library changed
|
|
|
+
|
|
|
+- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
|
|
+{
|
|
|
+ // Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+
|
|
|
+ PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:self.fetchResult];
|
|
|
+
|
|
|
+ if (changeDetails)
|
|
|
+ {
|
|
|
+ self.fetchResult = changeDetails.fetchResultAfterChanges;
|
|
|
+
|
|
|
+ UICollectionView *collectionView = self.collectionView;
|
|
|
+
|
|
|
+ if (!changeDetails.hasIncrementalChanges || changeDetails.hasMoves)
|
|
|
+ {
|
|
|
+ [collectionView reloadData];
|
|
|
+ [self resetCachedAssetImages];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ NSArray *removedPaths;
|
|
|
+ NSArray *insertedPaths;
|
|
|
+ NSArray *changedPaths;
|
|
|
+
|
|
|
+ NSIndexSet *removedIndexes = changeDetails.removedIndexes;
|
|
|
+ removedPaths = [removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
|
|
|
+
|
|
|
+ NSIndexSet *insertedIndexes = changeDetails.insertedIndexes;
|
|
|
+ insertedPaths = [insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
|
|
|
+
|
|
|
+ NSIndexSet *changedIndexes = changeDetails.changedIndexes;
|
|
|
+ changedPaths = [changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0];
|
|
|
+
|
|
|
+ BOOL shouldReload = NO;
|
|
|
+
|
|
|
+ if (changedPaths != nil && removedPaths != nil)
|
|
|
+ {
|
|
|
+ for (NSIndexPath *changedPath in changedPaths)
|
|
|
+ {
|
|
|
+ if ([removedPaths containsObject:changedPath])
|
|
|
+ {
|
|
|
+ shouldReload = YES;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.fetchResult.count)
|
|
|
+ {
|
|
|
+ shouldReload = YES;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shouldReload)
|
|
|
+ {
|
|
|
+ [collectionView reloadData];
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // if we have incremental diffs, tell the collection view to animate insertions and deletions
|
|
|
+ [collectionView performBatchUpdates:^{
|
|
|
+ if (removedPaths.count)
|
|
|
+ {
|
|
|
+ [collectionView deleteItemsAtIndexPaths:[removedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (insertedPaths.count)
|
|
|
+ {
|
|
|
+ [collectionView insertItemsAtIndexPaths:[insertedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0]];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (changedPaths.count)
|
|
|
+ {
|
|
|
+ [collectionView reloadItemsAtIndexPaths:[changedIndexes ctassetsPickerIndexPathsFromIndexesWithSection:0] ];
|
|
|
+ }
|
|
|
+ } completion:^(BOOL finished){
|
|
|
+ if (finished)
|
|
|
+ [self resetCachedAssetImages];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.footer bind:self.fetchResult];
|
|
|
+
|
|
|
+ if (self.fetchResult.count == 0)
|
|
|
+ [self showNoAssets];
|
|
|
+ else
|
|
|
+ [self hideNoAssets];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ([self.delegate respondsToSelector:@selector(assetsGridViewController:photoLibraryDidChangeForAssetCollection:)])
|
|
|
+ [self.delegate assetsGridViewController:self photoLibraryDidChangeForAssetCollection:self.assetCollection];
|
|
|
+
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Selected assets changed
|
|
|
+
|
|
|
+- (void)assetsPickerSelectedAssetsDidChange:(NSNotification *)notification
|
|
|
+{
|
|
|
+ NSArray *selectedAssets = (NSArray *)notification.object;
|
|
|
+ [self updateTitle:selectedAssets];
|
|
|
+ [self updateButton:selectedAssets];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateTitle:(NSArray *)selectedAssets
|
|
|
+{
|
|
|
+ if (selectedAssets.count > 0)
|
|
|
+ self.title = self.picker.selectedAssetsString;
|
|
|
+ else
|
|
|
+ self.title = self.assetCollection.localizedTitle;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateButton:(NSArray *)selectedAssets
|
|
|
+{
|
|
|
+ if (self.picker.alwaysEnableDoneButton)
|
|
|
+ self.navigationItem.rightBarButtonItem.enabled = YES;
|
|
|
+ else
|
|
|
+ self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Did de/select asset notifications
|
|
|
+
|
|
|
+- (void)assetsPickerDidSelectAsset:(NSNotification *)notification
|
|
|
+{
|
|
|
+ PHAsset *asset = (PHAsset *)notification.object;
|
|
|
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
|
|
|
+ [self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
|
|
+
|
|
|
+ [self updateSelectionOrderLabels];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)assetsPickerDidDeselectAsset:(NSNotification *)notification
|
|
|
+{
|
|
|
+ PHAsset *asset = (PHAsset *)notification.object;
|
|
|
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[self.fetchResult indexOfObject:asset] inSection:0];
|
|
|
+ [self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
|
|
|
+
|
|
|
+ [self updateSelectionOrderLabels];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Update Selection Order Labels
|
|
|
+
|
|
|
+- (void)updateSelectionOrderLabels
|
|
|
+{
|
|
|
+ for (NSIndexPath *indexPath in [self.collectionView indexPathsForSelectedItems])
|
|
|
+ {
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+ CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
|
|
|
+ cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Gesture recognizer
|
|
|
+
|
|
|
+- (void)addGestureRecognizer
|
|
|
+{
|
|
|
+ UILongPressGestureRecognizer *longPress =
|
|
|
+ [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pushPageViewController:)];
|
|
|
+
|
|
|
+ [self.collectionView addGestureRecognizer:longPress];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Push assets page view controller
|
|
|
+
|
|
|
+- (void)pushPageViewController:(UILongPressGestureRecognizer *)longPress
|
|
|
+{
|
|
|
+ if (longPress.state == UIGestureRecognizerStateBegan)
|
|
|
+ {
|
|
|
+ CGPoint point = [longPress locationInView:self.collectionView];
|
|
|
+ NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
|
|
|
+
|
|
|
+ CTAssetsPageViewController *vc = [[CTAssetsPageViewController alloc] initWithFetchResult:self.fetchResult];
|
|
|
+ vc.allowsSelection = YES;
|
|
|
+ vc.pageIndex = indexPath.item;
|
|
|
+
|
|
|
+ [self.navigationController pushViewController:vc animated:YES];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Reload data
|
|
|
+
|
|
|
+- (void)reloadData
|
|
|
+{
|
|
|
+ if (self.fetchResult.count > 0)
|
|
|
+ {
|
|
|
+ [self hideNoAssets];
|
|
|
+ [self.collectionView reloadData];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ [self showNoAssets];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Asset images caching
|
|
|
+
|
|
|
+- (void)resetCachedAssetImages
|
|
|
+{
|
|
|
+ [self.imageManager stopCachingImagesForAllAssets];
|
|
|
+ self.previousPreheatRect = CGRectZero;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateCachedAssetImages
|
|
|
+{
|
|
|
+ BOOL isViewVisible = [self isViewLoaded] && self.view.window != nil;
|
|
|
+
|
|
|
+ if (!isViewVisible)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // The preheat window is twice the height of the visible rect
|
|
|
+ CGRect preheatRect = self.collectionView.bounds;
|
|
|
+ preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
|
|
|
+
|
|
|
+ // If scrolled by a "reasonable" amount...
|
|
|
+ CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
|
|
|
+
|
|
|
+ if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f)
|
|
|
+ {
|
|
|
+ // Compute the assets to start caching and to stop caching.
|
|
|
+ NSMutableArray *addedIndexPaths = [NSMutableArray array];
|
|
|
+ NSMutableArray *removedIndexPaths = [NSMutableArray array];
|
|
|
+
|
|
|
+ [self computeDifferenceBetweenRect:self.previousPreheatRect
|
|
|
+ andRect:preheatRect
|
|
|
+ removedHandler:^(CGRect removedRect) {
|
|
|
+ NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:removedRect];
|
|
|
+ [removedIndexPaths addObjectsFromArray:indexPaths];
|
|
|
+ } addedHandler:^(CGRect addedRect) {
|
|
|
+ NSArray *indexPaths = [self.collectionView ctassetsPickerIndexPathsForElementsInRect:addedRect];
|
|
|
+ [addedIndexPaths addObjectsFromArray:indexPaths];
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self startCachingThumbnailsForIndexPaths:addedIndexPaths];
|
|
|
+ [self stopCachingThumbnailsForIndexPaths:removedIndexPaths];
|
|
|
+
|
|
|
+ self.previousPreheatRect = preheatRect;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)startCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
|
|
|
+{
|
|
|
+ for (NSIndexPath *indexPath in indexPaths)
|
|
|
+ {
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if (!asset) break;
|
|
|
+
|
|
|
+ UICollectionViewLayoutAttributes *attributes =
|
|
|
+ [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
|
|
|
+
|
|
|
+ [self.imageManager startCachingImagesForAssets:@[asset]
|
|
|
+ targetSize:targetSize
|
|
|
+ contentMode:PHImageContentModeAspectFill
|
|
|
+ options:self.picker.thumbnailRequestOptions];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)stopCachingThumbnailsForIndexPaths:(NSArray *)indexPaths
|
|
|
+{
|
|
|
+ for (NSIndexPath *indexPath in indexPaths)
|
|
|
+ {
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if (!asset) break;
|
|
|
+
|
|
|
+ UICollectionViewLayoutAttributes *attributes =
|
|
|
+ [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
|
|
|
+
|
|
|
+ [self.imageManager stopCachingImagesForAssets:@[asset]
|
|
|
+ targetSize:targetSize
|
|
|
+ contentMode:PHImageContentModeAspectFill
|
|
|
+ options:self.picker.thumbnailRequestOptions];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
|
|
|
+{
|
|
|
+ if (CGRectIntersectsRect(newRect, oldRect)) {
|
|
|
+ CGFloat oldMaxY = CGRectGetMaxY(oldRect);
|
|
|
+ CGFloat oldMinY = CGRectGetMinY(oldRect);
|
|
|
+ CGFloat newMaxY = CGRectGetMaxY(newRect);
|
|
|
+ CGFloat newMinY = CGRectGetMinY(newRect);
|
|
|
+ if (newMaxY > oldMaxY) {
|
|
|
+ CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
|
|
|
+ addedHandler(rectToAdd);
|
|
|
+ }
|
|
|
+ if (oldMinY > newMinY) {
|
|
|
+ CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
|
|
|
+ addedHandler(rectToAdd);
|
|
|
+ }
|
|
|
+ if (newMaxY < oldMaxY) {
|
|
|
+ CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
|
|
|
+ removedHandler(rectToRemove);
|
|
|
+ }
|
|
|
+ if (oldMinY < newMinY) {
|
|
|
+ CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
|
|
|
+ removedHandler(rectToRemove);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ addedHandler(newRect);
|
|
|
+ removedHandler(oldRect);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Scroll view delegate
|
|
|
+
|
|
|
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
|
+{
|
|
|
+ [self updateCachedAssetImages];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - No assets
|
|
|
+
|
|
|
+- (void)showNoAssets
|
|
|
+{
|
|
|
+ CTAssetsPickerNoAssetsView *view = [CTAssetsPickerNoAssetsView new];
|
|
|
+ [self.view addSubview:view];
|
|
|
+ [view setNeedsUpdateConstraints];
|
|
|
+ [view updateConstraintsIfNeeded];
|
|
|
+
|
|
|
+ self.noAssetsView = view;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)hideNoAssets
|
|
|
+{
|
|
|
+ if (self.noAssetsView)
|
|
|
+ {
|
|
|
+ [self.noAssetsView removeFromSuperview];
|
|
|
+ self.noAssetsView = nil;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Collection view data source
|
|
|
+
|
|
|
+- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
|
|
+{
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
|
+{
|
|
|
+ return self.fetchResult.count;
|
|
|
+}
|
|
|
+
|
|
|
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ CTAssetsGridViewCell *cell =
|
|
|
+ [collectionView dequeueReusableCellWithReuseIdentifier:CTAssetsGridViewCellIdentifier
|
|
|
+ forIndexPath:indexPath];
|
|
|
+
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)])
|
|
|
+ cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
|
|
|
+ else
|
|
|
+ cell.enabled = YES;
|
|
|
+
|
|
|
+ cell.showsSelectionIndex = self.picker.showsSelectionIndex;
|
|
|
+
|
|
|
+ // XXX
|
|
|
+ // Setting `selected` property blocks further deselection.
|
|
|
+ // Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
|
|
|
+ if ([self.picker.selectedAssets containsObject:asset])
|
|
|
+ {
|
|
|
+ cell.selected = YES;
|
|
|
+ cell.selectionIndex = [self.picker.selectedAssets indexOfObject:asset];
|
|
|
+ [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
|
|
+ }
|
|
|
+
|
|
|
+ [cell bind:asset];
|
|
|
+
|
|
|
+ UICollectionViewLayoutAttributes *attributes =
|
|
|
+ [collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ CGSize targetSize = [self.picker imageSizeForContainerSize:attributes.size];
|
|
|
+
|
|
|
+ [self requestThumbnailForCell:cell targetSize:targetSize asset:asset];
|
|
|
+
|
|
|
+ return cell;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)requestThumbnailForCell:(CTAssetsGridViewCell *)cell targetSize:(CGSize)targetSize asset:(PHAsset *)asset
|
|
|
+{
|
|
|
+ NSInteger tag = cell.tag + 1;
|
|
|
+ cell.tag = tag;
|
|
|
+
|
|
|
+ [self.imageManager ctassetsPickerRequestImageForAsset:asset
|
|
|
+ targetSize:targetSize
|
|
|
+ contentMode:PHImageContentModeAspectFill
|
|
|
+ options:self.picker.thumbnailRequestOptions
|
|
|
+ resultHandler:^(UIImage *image, NSDictionary *info){
|
|
|
+ // Only update the image if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
|
|
+ if (cell.tag == tag)
|
|
|
+ [(CTAssetThumbnailView *)cell.backgroundView bind:image asset:asset];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ CTAssetsGridViewFooter *footer =
|
|
|
+ [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
|
|
|
+ withReuseIdentifier:CTAssetsGridViewFooterIdentifier
|
|
|
+ forIndexPath:indexPath];
|
|
|
+
|
|
|
+ [footer bind:self.fetchResult];
|
|
|
+
|
|
|
+ self.footer = footer;
|
|
|
+
|
|
|
+ return footer;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark - Collection view delegate
|
|
|
+
|
|
|
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ CTAssetsGridViewCell *cell = (CTAssetsGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if (!cell.isEnabled)
|
|
|
+ return NO;
|
|
|
+ else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)])
|
|
|
+ return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
|
|
|
+ else
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ [self.picker selectAsset:asset];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)])
|
|
|
+ [self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)])
|
|
|
+ return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
|
|
|
+ else
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ [self.picker deselectAsset:asset];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)])
|
|
|
+ [self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)])
|
|
|
+ return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
|
|
|
+ else
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)])
|
|
|
+ [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
|
+{
|
|
|
+ PHAsset *asset = [self assetAtIndexPath:indexPath];
|
|
|
+
|
|
|
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)])
|
|
|
+ [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
|
|
|
+}
|
|
|
+
|
|
|
+@end
|