802 lines
28 KiB
Objective-C
Executable File
802 lines
28 KiB
Objective-C
Executable File
/*
|
|
|
|
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 |