417 lines
18 KiB
Mathematica
417 lines
18 KiB
Mathematica
|
//
|
||
|
// GMAlbumsViewController.m
|
||
|
// GMPhotoPicker
|
||
|
//
|
||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "GMImagePickerController.h"
|
||
|
#import "GMAlbumsViewController.h"
|
||
|
#import "GMGridViewCell.h"
|
||
|
#import "GMGridViewController.h"
|
||
|
#import "GMAlbumsViewCell.h"
|
||
|
|
||
|
#include <Photos/Photos.h>
|
||
|
|
||
|
@interface GMAlbumsViewController() <PHPhotoLibraryChangeObserver>
|
||
|
|
||
|
@property (strong,nonatomic) NSArray *collectionsFetchResults;
|
||
|
@property (strong,nonatomic) NSArray *collectionsLocalizedTitles;
|
||
|
@property (strong,nonatomic) NSArray *collectionsFetchResultsAssets;
|
||
|
@property (strong,nonatomic) NSArray *collectionsFetchResultsTitles;
|
||
|
@property (nonatomic, weak) GMImagePickerController *picker;
|
||
|
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
||
|
|
||
|
@end
|
||
|
|
||
|
|
||
|
@implementation GMAlbumsViewController
|
||
|
|
||
|
- (id)init
|
||
|
{
|
||
|
if (self = [super initWithStyle:UITableViewStylePlain]) {
|
||
|
self.preferredContentSize = kPopoverContentSize;
|
||
|
}
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
static NSString *const AllPhotosReuseIdentifier = @"AllPhotosCell";
|
||
|
static NSString *const CollectionCellReuseIdentifier = @"CollectionCell";
|
||
|
|
||
|
- (void)viewDidLoad
|
||
|
{
|
||
|
[super viewDidLoad];
|
||
|
|
||
|
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
||
|
|
||
|
// Navigation bar customization
|
||
|
if (self.picker.customNavigationBarPrompt) {
|
||
|
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
||
|
}
|
||
|
|
||
|
self.imageManager = [[PHCachingImageManager alloc] init];
|
||
|
|
||
|
// Table view aspect
|
||
|
self.tableView.rowHeight = kAlbumRowHeight;
|
||
|
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||
|
|
||
|
// Buttons
|
||
|
NSDictionary *barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
||
|
|
||
|
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
||
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
||
|
style:UIBarButtonItemStylePlain
|
||
|
target:self.picker
|
||
|
action:@selector(dismiss:)];
|
||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||
|
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||
|
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||
|
}
|
||
|
|
||
|
if (self.picker.allowsMultipleSelection) {
|
||
|
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
||
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
||
|
style:UIBarButtonItemStyleDone
|
||
|
target:self.picker
|
||
|
action:@selector(finishPickingAssets:)];
|
||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||
|
}
|
||
|
|
||
|
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
||
|
}
|
||
|
|
||
|
// Bottom toolbar
|
||
|
self.toolbarItems = self.picker.toolbarItems;
|
||
|
|
||
|
// Title
|
||
|
if (!self.picker.title) {
|
||
|
self.title = NSLocalizedStringFromTableInBundle(@"picker.navigation.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Navigation bar default title");
|
||
|
} else {
|
||
|
self.title = self.picker.title;
|
||
|
}
|
||
|
|
||
|
// Fetch PHAssetCollections:
|
||
|
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
|
||
|
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
|
||
|
self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
|
||
|
self.collectionsLocalizedTitles = @[NSLocalizedStringFromTableInBundle(@"picker.table.smart-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Smart Albums"),NSLocalizedStringFromTableInBundle(@"picker.table.user-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Albums")];
|
||
|
|
||
|
[self updateFetchResults];
|
||
|
|
||
|
// Register for changes
|
||
|
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
||
|
|
||
|
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
||
|
{
|
||
|
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)dealloc
|
||
|
{
|
||
|
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
||
|
}
|
||
|
|
||
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||
|
return self.picker.pickerStatusBarStyle;
|
||
|
}
|
||
|
|
||
|
- (void)selectAllAlbumsCell {
|
||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
|
||
|
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
|
||
|
}
|
||
|
|
||
|
-(void)updateFetchResults
|
||
|
{
|
||
|
//What I do here is fetch both the albums list and the assets of each album.
|
||
|
//This way I have acces to the number of items in each album, I can load the 3
|
||
|
//thumbnails directly and I can pass the fetched result to the gridViewController.
|
||
|
|
||
|
self.collectionsFetchResultsAssets=nil;
|
||
|
self.collectionsFetchResultsTitles=nil;
|
||
|
|
||
|
//Fetch PHAssetCollections:
|
||
|
PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
|
||
|
PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
|
||
|
|
||
|
//All album: Sorted by descending creation date.
|
||
|
NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
|
||
|
NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
|
||
|
{
|
||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||
|
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
|
||
|
[allFetchResultArray addObject:assetsFetchResult];
|
||
|
[allFetchResultLabel addObject:NSLocalizedStringFromTableInBundle(@"picker.table.all-photos-label", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"All photos")];
|
||
|
}
|
||
|
|
||
|
//User albums:
|
||
|
NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
|
||
|
NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
|
||
|
for(PHCollection *collection in topLevelUserCollections)
|
||
|
{
|
||
|
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||
|
{
|
||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||
|
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||
|
|
||
|
//Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
|
||
|
|
||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||
|
[userFetchResultArray addObject:assetsFetchResult];
|
||
|
[userFetchResultLabel addObject:collection.localizedTitle];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//Smart albums: Sorted by descending creation date.
|
||
|
NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
|
||
|
NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
|
||
|
for(PHCollection *collection in smartAlbums)
|
||
|
{
|
||
|
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||
|
{
|
||
|
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||
|
|
||
|
//Smart collections are PHAssetCollectionType=2;
|
||
|
if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
|
||
|
{
|
||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||
|
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||
|
|
||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||
|
if(assetsFetchResult.count>0)
|
||
|
{
|
||
|
[smartFetchResultArray addObject:assetsFetchResult];
|
||
|
[smartFetchResultLabel addObject:collection.localizedTitle];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.collectionsFetchResultsAssets= @[allFetchResultArray,smartFetchResultArray,userFetchResultArray];
|
||
|
self.collectionsFetchResultsTitles= @[allFetchResultLabel,smartFetchResultLabel,userFetchResultLabel];
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - Accessors
|
||
|
|
||
|
- (GMImagePickerController *)picker
|
||
|
{
|
||
|
return (GMImagePickerController *)self.navigationController.parentViewController;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - Rotation
|
||
|
|
||
|
- (BOOL)shouldAutorotate
|
||
|
{
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||
|
{
|
||
|
return UIInterfaceOrientationMaskAllButUpsideDown;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - UITableViewDataSource
|
||
|
|
||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||
|
{
|
||
|
return (NSInteger)self.collectionsFetchResultsAssets.count;
|
||
|
}
|
||
|
|
||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||
|
{
|
||
|
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||
|
return (NSInteger)fetchResult.count;
|
||
|
}
|
||
|
|
||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||
|
{
|
||
|
static NSString *CellIdentifier = @"Cell";
|
||
|
|
||
|
GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||
|
if (cell == nil) {
|
||
|
cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
||
|
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||
|
}
|
||
|
|
||
|
// Increment the cell's tag
|
||
|
NSInteger currentTag = cell.tag + 1;
|
||
|
cell.tag = currentTag;
|
||
|
|
||
|
// Set the label
|
||
|
cell.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize];
|
||
|
cell.textLabel.text = (self.collectionsFetchResultsTitles[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||
|
cell.textLabel.textColor = self.picker.pickerTextColor;
|
||
|
|
||
|
// Retrieve the pre-fetched assets for this album:
|
||
|
PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||
|
|
||
|
// Display the number of assets
|
||
|
if (self.picker.displayAlbumsNumberOfAssets) {
|
||
|
cell.detailTextLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||
|
cell.detailTextLabel.text = [self tableCellSubtitle:assetsFetchResult];
|
||
|
cell.detailTextLabel.textColor = self.picker.pickerTextColor;
|
||
|
}
|
||
|
|
||
|
// Set the 3 images (if exists):
|
||
|
if ([assetsFetchResult count] > 0) {
|
||
|
CGFloat scale = [UIScreen mainScreen].scale;
|
||
|
|
||
|
//Compute the thumbnail pixel size:
|
||
|
CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
|
||
|
PHAsset *asset = assetsFetchResult[0];
|
||
|
[cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
|
||
|
[self.imageManager requestImageForAsset:asset
|
||
|
targetSize:tableCellThumbnailSize1
|
||
|
contentMode:PHImageContentModeAspectFill
|
||
|
options:nil
|
||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||
|
if (cell.tag == currentTag) {
|
||
|
cell.imageView1.image = result;
|
||
|
}
|
||
|
}];
|
||
|
|
||
|
// Second & third images:
|
||
|
// TODO: Only preload the 3pixels height visible frame!
|
||
|
if ([assetsFetchResult count] > 1) {
|
||
|
//Compute the thumbnail pixel size:
|
||
|
CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
|
||
|
PHAsset *asset = assetsFetchResult[1];
|
||
|
[self.imageManager requestImageForAsset:asset
|
||
|
targetSize:tableCellThumbnailSize2
|
||
|
contentMode:PHImageContentModeAspectFill
|
||
|
options:nil
|
||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||
|
if (cell.tag == currentTag) {
|
||
|
cell.imageView2.image = result;
|
||
|
}
|
||
|
}];
|
||
|
} else {
|
||
|
cell.imageView2.image = nil;
|
||
|
}
|
||
|
|
||
|
if ([assetsFetchResult count] > 2) {
|
||
|
CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
|
||
|
PHAsset *asset = assetsFetchResult[2];
|
||
|
[self.imageManager requestImageForAsset:asset
|
||
|
targetSize:tableCellThumbnailSize3
|
||
|
contentMode:PHImageContentModeAspectFill
|
||
|
options:nil
|
||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||
|
if (cell.tag == currentTag) {
|
||
|
cell.imageView3.image = result;
|
||
|
}
|
||
|
}];
|
||
|
} else {
|
||
|
cell.imageView3.image = nil;
|
||
|
}
|
||
|
} else {
|
||
|
[cell setVideoLayout:NO];
|
||
|
cell.imageView3.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||
|
cell.imageView2.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||
|
cell.imageView1.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||
|
}
|
||
|
|
||
|
return cell;
|
||
|
}
|
||
|
|
||
|
#pragma mark - UITableViewDelegate
|
||
|
|
||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||
|
{
|
||
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||
|
|
||
|
// Init the GMGridViewController
|
||
|
GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
|
||
|
// Set the title
|
||
|
gridViewController.title = cell.textLabel.text;
|
||
|
// Use the prefetched assets!
|
||
|
gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:(NSUInteger)indexPath.section] objectAtIndex:(NSUInteger)indexPath.row];
|
||
|
|
||
|
// Remove selection so it looks better on slide in
|
||
|
[tableView deselectRowAtIndexPath:indexPath animated:true];
|
||
|
|
||
|
// Push GMGridViewController
|
||
|
[self.navigationController pushViewController:gridViewController animated:YES];
|
||
|
}
|
||
|
|
||
|
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
|
||
|
{
|
||
|
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
|
||
|
// header.contentView.backgroundColor = [UIColor clearColor];
|
||
|
// header.backgroundView.backgroundColor = [UIColor clearColor];
|
||
|
|
||
|
// Default is a bold font, but keep this styled as a normal font
|
||
|
header.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||
|
header.textLabel.textColor = self.picker.pickerTextColor;
|
||
|
}
|
||
|
|
||
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||
|
{
|
||
|
//Tip: Returning nil hides the section header!
|
||
|
|
||
|
NSString *title = nil;
|
||
|
if (section > 0) {
|
||
|
// Only show title for non-empty sections:
|
||
|
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||
|
if (fetchResult.count > 0) {
|
||
|
title = self.collectionsLocalizedTitles[(NSUInteger)(section - 1)];
|
||
|
}
|
||
|
}
|
||
|
return title;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma mark - PHPhotoLibraryChangeObserver
|
||
|
|
||
|
- (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(), ^{
|
||
|
|
||
|
NSMutableArray *updatedCollectionsFetchResults = nil;
|
||
|
|
||
|
for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
|
||
|
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
|
||
|
if (changeDetails) {
|
||
|
if (!updatedCollectionsFetchResults) {
|
||
|
updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
|
||
|
}
|
||
|
[updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This only affects to changes in albums level (add/remove/edit album)
|
||
|
if (updatedCollectionsFetchResults) {
|
||
|
self.collectionsFetchResults = updatedCollectionsFetchResults;
|
||
|
}
|
||
|
|
||
|
// However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
|
||
|
// Maybe some checks could be done here , but for now is OKey.
|
||
|
[self updateFetchResults];
|
||
|
[self.tableView reloadData];
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
#pragma mark - Cell Subtitle
|
||
|
|
||
|
- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
|
||
|
{
|
||
|
// Just return the number of assets. Album app does this:
|
||
|
return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
|
||
|
}
|
||
|
|
||
|
@end
|