Browse Source

Initial rewrite/redesign of compose screen

Still pending:
- Posting videos
- Image resizing
- Attachment drawer thing
pixelfed
Shadowfacts 3 years ago
parent
commit
b8430be00c
Signed by: shadowfacts GPG Key ID: 94A5AB95422746E5
  1. BIN
      GMImagePicker/Base.lproj/GMImagePicker.strings
  2. 32
      GMImagePicker/GMAlbumsViewCell.h
  3. 131
      GMImagePicker/GMAlbumsViewCell.m
  4. 32
      GMImagePicker/GMAlbumsViewController.h
  5. 416
      GMImagePicker/GMAlbumsViewController.m
  6. BIN
      GMImagePicker/GMEmptyFolder@1x.png
  7. BIN
      GMImagePicker/GMEmptyFolder@2x.png
  8. 32
      GMImagePicker/GMGridViewCell.h
  9. 176
      GMImagePicker/GMGridViewCell.m
  10. 21
      GMImagePicker/GMGridViewController.h
  11. 611
      GMImagePicker/GMGridViewController.m
  12. 24
      GMImagePicker/GMImagePicker.h
  13. 332
      GMImagePicker/GMImagePickerController.h
  14. 388
      GMImagePicker/GMImagePickerController.m
  15. BIN
      GMImagePicker/GMSelected.png
  16. BIN
      GMImagePicker/GMSelected@2x.png
  17. BIN
      GMImagePicker/GMVideoIcon.png
  18. BIN
      GMImagePicker/GMVideoIcon@2x.png
  19. 22
      GMImagePicker/Info.plist
  20. BIN
      GMImagePicker/ca.lproj/GMImagePicker.strings
  21. BIN
      GMImagePicker/de.lproj/GMImagePicker.strings
  22. BIN
      GMImagePicker/en.lproj/GMImagePicker.strings
  23. BIN
      GMImagePicker/es.lproj/GMImagePicker.strings
  24. BIN
      GMImagePicker/fr.lproj/GMImagePicker.strings
  25. BIN
      GMImagePicker/it.lproj/GMImagePicker.strings
  26. BIN
      GMImagePicker/pt.lproj/GMImagePicker.strings
  27. 436
      Tusker.xcodeproj/project.pbxproj
  28. 23
      Tusker/Extensions/Array+Uniques.swift
  29. 1
      Tusker/Preferences/Preferences.swift
  30. 601
      Tusker/Screens/Compose/ComposeViewController.swift
  31. 311
      Tusker/Screens/Compose/ComposeViewController.xib
  32. 74
      Tusker/Screens/Compose/StatusFormat.swift
  33. 56
      Tusker/Screens/Preferences/AdvancedTableViewController.swift
  34. 42
      Tusker/Screens/Preferences/Preferences.storyboard
  35. 69
      Tusker/Views/Account Detail/LargeAccountDetailView.swift
  36. 57
      Tusker/Views/Compose Media/ComposeMediaView.swift
  37. 90
      Tusker/Views/Compose Media/ComposeMediaView.xib
  38. 46
      Tusker/Views/Compose Status Reply/ComposeStatusReplyView.swift
  39. 81
      Tusker/Views/Compose Status Reply/ComposeStatusReplyView.xib
  40. 57
      Tusker/Views/ComposeMediaView.swift

BIN
GMImagePicker/Base.lproj/GMImagePicker.strings

Binary file not shown.

32
GMImagePicker/GMAlbumsViewCell.h

@ -0,0 +1,32 @@
//
// GMAlbumsViewCell.h
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 22/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#include <UIKit/UIKit.h>
#include <Photos/Photos.h>
@interface GMAlbumsViewCell : UITableViewCell
@property (strong) PHFetchResult *assetsFetchResults;
@property (strong) PHAssetCollection *assetCollection;
//The labels
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *infoLabel;
//The imageView
@property (nonatomic, strong) UIImageView *imageView1;
@property (nonatomic, strong) UIImageView *imageView2;
@property (nonatomic, strong) UIImageView *imageView3;
//Video additional information
@property (nonatomic, strong) UIImageView *videoIcon;
@property (nonatomic, strong) UIImageView *slowMoIcon;
@property (nonatomic, strong) UIView *gradientView;
@property (nonatomic, strong) CAGradientLayer *gradient;
//Selection overlay
- (void)setVideoLayout:(BOOL)isVideo;
@end

131
GMImagePicker/GMAlbumsViewCell.m

@ -0,0 +1,131 @@
//
// GMAlbumsViewCell.m
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 22/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#import "GMAlbumsViewCell.h"
#import "GMAlbumsViewController.h"
#import "GMImagePickerController.h"
#import <QuartzCore/QuartzCore.h>
@implementation GMAlbumsViewCell
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
{
// self.isAccessibilityElement = YES;
self.contentView.backgroundColor = [UIColor clearColor];
self.backgroundColor = [UIColor clearColor];
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Border width of 1 pixel:
float borderWidth = 1.0/[UIScreen mainScreen].scale;
// ImageView
_imageView3 = [UIImageView new];
_imageView3.contentMode = UIViewContentModeScaleAspectFill;
_imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
[_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[_imageView3.layer setBorderWidth: borderWidth];
_imageView3.clipsToBounds = YES;
_imageView3.translatesAutoresizingMaskIntoConstraints = YES;
_imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
[self.contentView addSubview:_imageView3];
// ImageView
_imageView2 = [UIImageView new];
_imageView2.contentMode = UIViewContentModeScaleAspectFill;
_imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
[_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[_imageView2.layer setBorderWidth: borderWidth];
_imageView2.clipsToBounds = YES;
_imageView2.translatesAutoresizingMaskIntoConstraints = YES;
_imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
[self.contentView addSubview:_imageView2];
// ImageView
_imageView1 = [UIImageView new];
_imageView1.contentMode = UIViewContentModeScaleAspectFill;
_imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
[_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[_imageView1.layer setBorderWidth: borderWidth];
_imageView1.clipsToBounds = YES;
_imageView1.translatesAutoresizingMaskIntoConstraints = YES;
_imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
[self.contentView addSubview:_imageView1];
// The video gradient, label & icon
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
_gradient = [CAGradientLayer layer];
_gradient.frame = _gradientView.bounds;
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
_gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
[_gradientView.layer insertSublayer:_gradient atIndex:0];
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
_gradientView.translatesAutoresizingMaskIntoConstraints = YES;
[self.imageView1 addSubview:_gradientView];
_gradientView.hidden = YES;
// VideoIcon
_videoIcon = [UIImageView new];
_videoIcon.contentMode = UIViewContentModeScaleAspectFill;
_videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMAlbumsViewCell.class] compatibleWithTraitCollection:nil];
_videoIcon.clipsToBounds = YES;
_videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
[self.imageView1 addSubview:_videoIcon];
_videoIcon.hidden = NO;
// TextLabel
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
self.textLabel.numberOfLines = 1;
self.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
self.detailTextLabel.numberOfLines = 1;
}
return self;
}
-(void)layoutSubviews {
[super layoutSubviews];
self.textLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.textLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8, self.textLabel.frame.size.height);
self.detailTextLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.detailTextLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8 - kAlbumImageToTextSpace, self.detailTextLabel.frame.size.height);
}
- (void)setVideoLayout:(BOOL)isVideo
{
// TODO : Add additional icons for slowmo, burst, etc...
if (isVideo) {
_videoIcon.hidden = NO;
_gradientView.hidden = NO;
} else {
_videoIcon.hidden = YES;
_gradientView.hidden = YES;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
@end

32
GMImagePicker/GMAlbumsViewController.h

@ -0,0 +1,32 @@
//
// GMAlbumsViewController.h
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 19/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#import <UIKit/UIKit.h>
// Measuring IOS8 Photos APP at @2x (iPhone5s):
// The rows are 180px/90pts
// Left image border is 21px/10.5pts
// Separation between image and text is 42px/21pts (double the previouse one)
// The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
// The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
// The third image measures 123px/61.5pts including 1px/0.5pts white border. Only 3px/1.5pts visible
static int kAlbumRowHeight = 90;
static int kAlbumLeftToImageSpace = 10;
static int kAlbumImageToTextSpace = 21;
static float const kAlbumGradientHeight = 20.0f;
static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
@interface GMAlbumsViewController : UITableViewController
- (void)selectAllAlbumsCell;
@end

416
GMImagePicker/GMAlbumsViewController.m

@ -0,0 +1,416 @@
//
// 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

BIN
GMImagePicker/GMEmptyFolder@1x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
GMImagePicker/GMEmptyFolder@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

32
GMImagePicker/GMGridViewCell.h

@ -0,0 +1,32 @@
//
// GMGridViewCell.h
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 19/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#include <UIKit/UIKit.h>
#include <Photos/Photos.h>
@interface GMGridViewCell : UICollectionViewCell
@property (nonatomic, strong) PHAsset *asset;
//The imageView
@property (nonatomic, strong) UIImageView *imageView;
//Video additional information
@property (nonatomic, strong) UIImageView *videoIcon;
@property (nonatomic, strong) UILabel *videoDuration;
@property (nonatomic, strong) UIView *gradientView;
@property (nonatomic, strong) CAGradientLayer *gradient;
//Selection overlay
@property (nonatomic) BOOL shouldShowSelection;
@property (nonatomic, strong) UIView *coverView;
@property (nonatomic, strong) UIButton *selectedButton;
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
- (void)bind:(PHAsset *)asset;
@end

176
GMImagePicker/GMGridViewCell.m

@ -0,0 +1,176 @@
//
// GMGridViewCell.m
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 19/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#import "GMGridViewCell.h"
@interface GMGridViewCell ()
@end
@implementation GMGridViewCell
static UIFont *titleFont;
static CGFloat titleHeight;
static UIImage *videoIcon;
static UIColor *titleColor;
static UIImage *checkedIcon;
static UIColor *selectedColor;
static UIColor *disabledColor;
+ (void)initialize
{
titleFont = [UIFont systemFontOfSize:12];
titleHeight = 20.0f;
videoIcon = [UIImage imageNamed:@"GMImagePickerVideo"];
titleColor = [UIColor whiteColor];
checkedIcon = [UIImage imageNamed:@"CTAssetsPickerChecked"];
selectedColor = [UIColor colorWithWhite:1 alpha:0.3];
disabledColor = [UIColor colorWithWhite:1 alpha:0.9];
}
- (void)awakeFromNib
{
[super awakeFromNib];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.opaque = NO;
self.enabled = YES;
CGFloat cellSize = self.contentView.bounds.size.width;
// The image view
_imageView = [UIImageView new];
_imageView.frame = CGRectMake(0, 0, cellSize, cellSize);
_imageView.contentMode = UIViewContentModeScaleAspectFill;
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
_imageView.contentMode = UIViewContentModeScaleAspectFit;
}
else
{
_imageView.contentMode = UIViewContentModeScaleAspectFill;
}*/
_imageView.clipsToBounds = YES;
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
_imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self addSubview:_imageView];
// The video gradient, label & icon
float x_offset = 4.0f;
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.8];
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, self.bounds.size.height-titleHeight, self.bounds.size.width, titleHeight)];
_gradient = [CAGradientLayer layer];
_gradient.frame = _gradientView.bounds;
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[botGradient CGColor], nil];
[_gradientView.layer insertSublayer:_gradient atIndex:0];
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
_gradientView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_gradientView];
_gradientView.hidden = YES;
_videoIcon = [UIImageView new];
_videoIcon.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
_videoIcon.contentMode = UIViewContentModeLeft;
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil];
_videoIcon.translatesAutoresizingMaskIntoConstraints = NO;
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self addSubview:_videoIcon];
_videoIcon.hidden = YES;
_videoDuration = [UILabel new];
_videoDuration.font = titleFont;
_videoDuration.textColor = titleColor;
_videoDuration.textAlignment = NSTextAlignmentRight;
_videoDuration.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
_videoDuration.contentMode = UIViewContentModeRight;
_videoDuration.translatesAutoresizingMaskIntoConstraints = NO;
_videoDuration.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
[self addSubview:_videoDuration];
_videoDuration.hidden = YES;
// Selection overlay & icon
_coverView = [[UIView alloc] initWithFrame:self.bounds];
_coverView.translatesAutoresizingMaskIntoConstraints = NO;
_coverView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_coverView.backgroundColor = [UIColor colorWithRed:0.24 green:0.47 blue:0.85 alpha:0.6];
[self addSubview:_coverView];
_coverView.hidden = YES;
_selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
_selectedButton.frame = CGRectMake(2*self.bounds.size.width/3, 0*self.bounds.size.width/3, self.bounds.size.width/3, self.bounds.size.width/3);
_selectedButton.contentMode = UIViewContentModeTopRight;
_selectedButton.adjustsImageWhenHighlighted = NO;
[_selectedButton setImage:nil forState:UIControlStateNormal];
_selectedButton.translatesAutoresizingMaskIntoConstraints = NO;
_selectedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[_selectedButton setImage:[UIImage imageNamed:@"GMSelected" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil] forState:UIControlStateSelected];
_selectedButton.hidden = NO;
_selectedButton.userInteractionEnabled = NO;
[self addSubview:_selectedButton];
}
// Note: the views above are created in case this is toggled per cell, on the fly, etc.!
self.shouldShowSelection = YES;
return self;
}
// Required to resize the CAGradientLayer because it does not support auto resizing.
- (void)layoutSubviews {
[super layoutSubviews];
_gradient.frame = _gradientView.bounds;
}
- (void)bind:(PHAsset *)asset
{
self.asset = asset;
if (self.asset.mediaType == PHAssetMediaTypeVideo) {
_videoIcon.hidden = NO;
_videoDuration.hidden = NO;
_gradientView.hidden = NO;
_videoDuration.text = [self getDurationWithFormat:self.asset.duration];
} else {
_videoIcon.hidden = YES;
_videoDuration.hidden = YES;
_gradientView.hidden = YES;
}
}
// Override setSelected
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (!self.shouldShowSelection) {
return;
}
_coverView.hidden = !selected;
_selectedButton.selected = selected;
}
-(NSString*)getDurationWithFormat:(NSTimeInterval)duration
{
NSInteger ti = (NSInteger)duration;
NSInteger seconds = ti % 60;
NSInteger minutes = (ti / 60) % 60;
//NSInteger hours = (ti / 3600);
return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
}
@end

21
GMImagePicker/GMGridViewController.h

@ -0,0 +1,21 @@
//
// GMGridViewController.h
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 19/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#import "GMImagePickerController.h"
#include <UIKit/UIKit.h>
#include <Photos/Photos.h>
@interface GMGridViewController : UICollectionViewController
@property (strong,nonatomic) PHFetchResult *assetsFetchResults;
-(id)initWithPicker:(GMImagePickerController *)picker;
@end

611
GMImagePicker/GMGridViewController.m

@ -0,0 +1,611 @@
//
// GMGridViewController.m
// GMPhotoPicker
//
// Created by Guillermo Muntaner Perelló on 19/09/14.
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
//
#import "GMGridViewController.h"
#import "GMImagePickerController.h"
#import "GMAlbumsViewController.h"
#import "GMGridViewCell.h"
#include <Photos/Photos.h>
//Helper methods
@implementation NSIndexSet (Convenience)
- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexPaths addObject:[NSIndexPath indexPathForItem:(NSInteger)idx inSection:(NSInteger)section]];
}];
return indexPaths;
}
@end
@implementation UICollectionView (Convenience)
- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
if (allLayoutAttributes.count == 0) { return nil; }
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
NSIndexPath *indexPath = layoutAttributes.indexPath;
[indexPaths addObject:indexPath];
}
return indexPaths;
}
@end
@interface GMImagePickerController ()
- (void)finishPickingAssets:(id)sender;
- (void)dismiss:(id)sender;
- (NSString *)toolbarTitle;
- (UIView *)noAssetsView;
@end
@interface GMGridViewController () <PHPhotoLibraryChangeObserver>
@property (nonatomic, weak) GMImagePickerController *picker;
@property (strong,nonatomic) PHCachingImageManager *imageManager;
@property (assign, nonatomic) CGRect previousPreheatRect;
@end
static CGSize AssetGridThumbnailSize;
NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
@implementation GMGridViewController
{
CGFloat screenWidth;
CGFloat screenHeight;
UICollectionViewFlowLayout *portraitLayout;
UICollectionViewFlowLayout *landscapeLayout;
}
-(id)initWithPicker:(GMImagePickerController *)picker
{
//Custom init. The picker contains custom information to create the FlowLayout
self.picker = picker;
//Ipad popover is not affected by rotation!
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
screenWidth = CGRectGetWidth(picker.view.bounds);
screenHeight = CGRectGetHeight(picker.view.bounds);
}
else
{
if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
{
screenHeight = CGRectGetWidth(picker.view.bounds);
screenWidth = CGRectGetHeight(picker.view.bounds);
}
else
{
screenWidth = CGRectGetWidth(picker.view.bounds);
screenHeight = CGRectGetHeight(picker.view.bounds);
}
}
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
if (self = [super initWithCollectionViewLayout:layout])
{
//Compute the thumbnail pixel size:
CGFloat scale = [UIScreen mainScreen].scale;
//NSLog(@"This is @%fx scale device", scale);
if(scale >= 3)
{
scale = 2;
}
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection;
[self.collectionView registerClass:GMGridViewCell.class
forCellWithReuseIdentifier:GMGridViewCellIdentifier];
self.preferredContentSize = kPopoverContentSize;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViews];
// Navigation bar customization
if (self.picker.customNavigationBarPrompt) {
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
}
self.imageManager = [[PHCachingImageManager alloc] init];
[self resetCachedAssets];
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
{
self.edgesForExtendedLayout = UIRectEdgeNone;
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupButtons];
[self setupToolbar];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self updateCachedAssets];
}
- (void)dealloc
{
[self resetCachedAssets];
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return self.picker.pickerStatusBarStyle;
}
#pragma mark - Rotation
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return;
}
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
//Update the AssetGridThumbnailSize:
CGFloat scale = [UIScreen mainScreen].scale;
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
[self resetCachedAssets];
//This is optional. Reload visible thumbnails:
for (GMGridViewCell *cell in [self.collectionView visibleCells]) {
NSInteger currentTag = cell.tag;
[self.imageManager requestImageForAsset:cell.asset
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage *result, NSDictionary *info)
{
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
if (cell.tag == currentTag) {
[cell.imageView setImage:result];
}
}];
}
[self.collectionView setCollectionViewLayout:layout animated:YES];
}
#pragma mark - Setup
- (void)setupViews
{
self.collectionView.backgroundColor = [UIColor clearColor];
self.view.backgroundColor = [self.picker pickerBackgroundColor];
}
- (void)setupButtons
{
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:)];
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
} else {
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
style:UIBarButtonItemStyleDone
target:self.picker
action:@selector(dismiss:)];
}
if (self.picker.useCustomFontForNavigationBar) {
if (self.picker.useCustomFontForNavigationBar) {
NSDictionary* barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
}
}
}
- (void)setupToolbar
{
self.toolbarItems = self.picker.toolbarItems;
}
#pragma mark - Collection View Layout
- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
if(!portraitLayout)
{
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing);
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
portraitLayout.minimumLineSpacing = spaceWidth;
}
return portraitLayout;
}
else
{
if(UIInterfaceOrientationIsLandscape(orientation))
{
if(!landscapeLayout)
{
landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
int cellTotalUsableWidth = (int)(screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing);
landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
landscapeLayout.minimumLineSpacing = spaceWidth;
}
return landscapeLayout;
}
else
{
if(!portraitLayout)
{
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1) * self.picker.minimumInteritemSpacing);
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
portraitLayout.minimumLineSpacing = spaceWidth;
}
return portraitLayout;
}
}
}
#pragma mark - Collection View Data Source
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
forIndexPath:indexPath];
// Increment the cell's tag
NSInteger currentTag = cell.tag + 1;
cell.tag = currentTag;
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
NSLog(@"Image manager: Requesting FIT image for iPad");
[self.imageManager requestImageForAsset:asset
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFit
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
if (cell.tag == currentTag) {
[cell.imageView setImage:result];
}
}];
}
else*/
{
//NSLog(@"Image manager: Requesting FILL image for iPhone");
[self.imageManager requestImageForAsset:asset
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil
resultHandler:^(UIImage *result, NSDictionary *info) {
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
if (cell.tag == currentTag) {
[cell.imageView setImage:result];
}
}];
}
[cell bind:asset];
cell.shouldShowSelection = self.picker.allowsMultipleSelection;
// Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)]) {
cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
} else {
cell.enabled = YES;
}
// 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;
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
} else {
cell.selected = NO;
}
return cell;
}
#pragma mark - Collection View Delegate
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
GMGridViewCell *cell = (GMGridViewCell *)[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.assetsFetchResults[(NSUInteger)indexPath.item];
[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.assetsFetchResults[(NSUInteger)indexPath.item];
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.assetsFetchResults[(NSUInteger)indexPath.item];
[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.assetsFetchResults[(NSUInteger)indexPath.item];
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.assetsFetchResults[(NSUInteger)indexPath.item];
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.assetsFetchResults[(NSUInteger)indexPath.item];
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) {
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSInteger count = (NSInteger)self.assetsFetchResults.count;
return count;
}
#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(), ^{
// check if there are changes to the assets (insertions, deletions, updates)
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges) {
// get the new fetch result
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
// we need to reload all if the incremental diffs are not available
[collectionView reloadData];
} else {
// if we have incremental diffs, tell the collection view to animate insertions and deletions
[collectionView performBatchUpdates:^{
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
if ([removedIndexes count]) {
[collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
}
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
if ([insertedIndexes count]) {
[collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
if (self.picker.showCameraButton && self.picker.autoSelectCameraImages) {
for (NSIndexPath *path in [insertedIndexes aapl_indexPathsFromIndexesWithSection:0]) {
[self collectionView:collectionView didSelectItemAtIndexPath:path];
}
}
}
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count]) {
[collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
}
} completion:NULL];
}
[self resetCachedAssets];
}
});
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateCachedAssets];
}
#pragma mark - Asset Caching
- (void)resetCachedAssets
{
[self.imageManager stopCachingImagesForAllAssets];
self.previousPreheatRect = CGRectZero;
}
- (void)updateCachedAssets
{
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 aapl_indexPathsForElementsInRect:removedRect];
[removedIndexPaths addObjectsFromArray:indexPaths];
} addedHandler:^(CGRect addedRect) {
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
[addedIndexPaths addObjectsFromArray:indexPaths];
}];
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
targetSize:AssetGridThumbnailSize
contentMode:PHImageContentModeAspectFill
options:nil];
self.previousPreheatRect = preheatRect;
}
}
- (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);
}
}
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
{
if (indexPaths.count == 0) { return nil; }
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
[assets addObject:asset];
}
return assets;
}
@end

24
GMImagePicker/GMImagePicker.h

@ -0,0 +1,24 @@
//
// GMImagePicker.h
// GMImagePicker
//
// Created by Shadowfacts on 1/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for GMImagePicker.
FOUNDATION_EXPORT double GMImagePickerVersionNumber;
//! Project version string for GMImagePicker.
FOUNDATION_EXPORT const unsigned char GMImagePickerVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <GMImagePicker/PublicHeader.h>
#import <GMImagePicker/GMImagePickerController.h>
#import <GMImagePicker/GMAlbumsViewCell.h>
#import <GMImagePicker/GMAlbumsViewController.h>
#import <GMImagePicker/GMGridViewCell.h>
#import <GMImagePicker/GMGridViewController.h>

332
GMImagePicker/GMImagePickerController.h