From 3ecbb1895cafbdf4c6a84907d413f72d94af90ed Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 4 Jan 2020 16:25:15 -0500 Subject: [PATCH] Replace GMImagePicker with custom asset picker based on SheetController Fixes #23 Closes #50 --- .../Base.lproj/GMImagePicker.strings | Bin 1628 -> 0 bytes GMImagePicker/GMAlbumsViewCell.h | 32 - GMImagePicker/GMAlbumsViewCell.m | 131 ---- GMImagePicker/GMAlbumsViewController.h | 32 - GMImagePicker/GMAlbumsViewController.m | 416 ------------ GMImagePicker/GMEmptyFolder@1x.png | Bin 2932 -> 0 bytes GMImagePicker/GMEmptyFolder@2x.png | Bin 3077 -> 0 bytes GMImagePicker/GMGridViewCell.h | 32 - GMImagePicker/GMGridViewCell.m | 176 ----- GMImagePicker/GMGridViewController.h | 21 - GMImagePicker/GMGridViewController.m | 611 ------------------ GMImagePicker/GMImagePicker.h | 24 - GMImagePicker/GMImagePickerController.h | 332 ---------- GMImagePicker/GMImagePickerController.m | 388 ----------- GMImagePicker/GMSelected.png | Bin 2141 -> 0 bytes GMImagePicker/GMSelected@2x.png | Bin 3543 -> 0 bytes GMImagePicker/GMVideoIcon.png | Bin 158 -> 0 bytes GMImagePicker/GMVideoIcon@2x.png | Bin 194 -> 0 bytes GMImagePicker/Info.plist | 22 - GMImagePicker/ca.lproj/GMImagePicker.strings | Bin 1682 -> 0 bytes GMImagePicker/de.lproj/GMImagePicker.strings | Bin 1668 -> 0 bytes GMImagePicker/en.lproj/GMImagePicker.strings | Bin 2346 -> 0 bytes GMImagePicker/es.lproj/GMImagePicker.strings | Bin 1610 -> 0 bytes GMImagePicker/fr.lproj/GMImagePicker.strings | Bin 1698 -> 0 bytes GMImagePicker/it.lproj/GMImagePicker.strings | Bin 1656 -> 0 bytes GMImagePicker/pt.lproj/GMImagePicker.strings | Bin 1586 -> 0 bytes Tusker.xcodeproj/project.pbxproj | 354 +++------- .../xcshareddata/swiftpm/Package.resolved | 16 + Tusker/DraftsManager.swift | 3 +- Tusker/Info.plist | 4 +- .../AlbumAssetCollectionViewController.swift | 30 + .../AssetCollectionViewController.swift | 186 ++++++ .../AssetCollectionsListViewController.swift | 134 ++++ ...etPickerSheetContainerViewController.swift | 68 ++ .../AssetPickerViewController.swift | 95 +++ .../AssetPreviewViewController.swift | 118 ++++ .../Compose/ComposeViewController.swift | 112 ++-- .../Compose/CompositionAttachment.swift | 183 ++++++ .../Compose/Drafts/DraftTableViewCell.swift | 17 +- .../Asset Picker/AlbumTableViewCell.swift | 42 ++ .../Views/Asset Picker/AlbumTableViewCell.xib | 50 ++ .../Asset Picker/AllPhotosTableViewCell.swift | 37 ++ .../Asset Picker/AllPhotosTableViewCell.xib | 49 ++ .../AssetCollectionViewCell.swift | 74 +++ .../Asset Picker/AssetCollectionViewCell.xib | 121 ++++ .../ShowCameraCollectionViewCell.xib | 37 ++ .../Compose Media/ComposeMediaView.swift | 25 +- 47 files changed, 1407 insertions(+), 2565 deletions(-) delete mode 100644 GMImagePicker/Base.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/GMAlbumsViewCell.h delete mode 100644 GMImagePicker/GMAlbumsViewCell.m delete mode 100644 GMImagePicker/GMAlbumsViewController.h delete mode 100644 GMImagePicker/GMAlbumsViewController.m delete mode 100644 GMImagePicker/GMEmptyFolder@1x.png delete mode 100644 GMImagePicker/GMEmptyFolder@2x.png delete mode 100644 GMImagePicker/GMGridViewCell.h delete mode 100644 GMImagePicker/GMGridViewCell.m delete mode 100644 GMImagePicker/GMGridViewController.h delete mode 100644 GMImagePicker/GMGridViewController.m delete mode 100644 GMImagePicker/GMImagePicker.h delete mode 100644 GMImagePicker/GMImagePickerController.h delete mode 100644 GMImagePicker/GMImagePickerController.m delete mode 100755 GMImagePicker/GMSelected.png delete mode 100755 GMImagePicker/GMSelected@2x.png delete mode 100644 GMImagePicker/GMVideoIcon.png delete mode 100644 GMImagePicker/GMVideoIcon@2x.png delete mode 100644 GMImagePicker/Info.plist delete mode 100644 GMImagePicker/ca.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/de.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/en.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/es.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/fr.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/it.lproj/GMImagePicker.strings delete mode 100644 GMImagePicker/pt.lproj/GMImagePicker.strings create mode 100644 Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Tusker/Screens/Compose/Asset Picker/AlbumAssetCollectionViewController.swift create mode 100644 Tusker/Screens/Compose/Asset Picker/AssetCollectionViewController.swift create mode 100644 Tusker/Screens/Compose/Asset Picker/AssetCollectionsListViewController.swift create mode 100644 Tusker/Screens/Compose/Asset Picker/AssetPickerSheetContainerViewController.swift create mode 100644 Tusker/Screens/Compose/Asset Picker/AssetPickerViewController.swift create mode 100644 Tusker/Screens/Compose/Asset Picker/AssetPreviewViewController.swift create mode 100644 Tusker/Screens/Compose/CompositionAttachment.swift create mode 100644 Tusker/Views/Asset Picker/AlbumTableViewCell.swift create mode 100644 Tusker/Views/Asset Picker/AlbumTableViewCell.xib create mode 100644 Tusker/Views/Asset Picker/AllPhotosTableViewCell.swift create mode 100644 Tusker/Views/Asset Picker/AllPhotosTableViewCell.xib create mode 100644 Tusker/Views/Asset Picker/AssetCollectionViewCell.swift create mode 100644 Tusker/Views/Asset Picker/AssetCollectionViewCell.xib create mode 100644 Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib diff --git a/GMImagePicker/Base.lproj/GMImagePicker.strings b/GMImagePicker/Base.lproj/GMImagePicker.strings deleted file mode 100644 index cd03baa69f1031a7b174da3cd2e140a5031bfb9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1628 zcmcJP-A=+l5Jtaio}y{u4M`zh8xx5dL!v;0c+XEoq9AFDkFTC7;C8VVi5Nn5JG(RA znVp^ecyFq%K$jY7p|N7U@Ksk+HHBJghV2<^q6X_jyEtr+25+3!nEL? ztt;-%((&z6`_ATaIVPJsJ)f;g&1Y+orM*`!OS_Hb9g@*~qx@7F%*-$H{30y2!}`tf z*t+ao8l79I{In~xTq6cCucDs@SPoI~h1;+rb fOLcY2@0!_wSEH*A?}g8HD9?(1HpSo3R1 -#include - -@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 diff --git a/GMImagePicker/GMAlbumsViewCell.m b/GMImagePicker/GMAlbumsViewCell.m deleted file mode 100644 index afe8969d..00000000 --- a/GMImagePicker/GMAlbumsViewCell.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// 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 - -@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 diff --git a/GMImagePicker/GMAlbumsViewController.h b/GMImagePicker/GMAlbumsViewController.h deleted file mode 100644 index d60dc28c..00000000 --- a/GMImagePicker/GMAlbumsViewController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// GMAlbumsViewController.h -// GMPhotoPicker -// -// Created by Guillermo Muntaner Perelló on 19/09/14. -// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved. -// - -#import - -// 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 diff --git a/GMImagePicker/GMAlbumsViewController.m b/GMImagePicker/GMAlbumsViewController.m deleted file mode 100644 index 26dd151e..00000000 --- a/GMImagePicker/GMAlbumsViewController.m +++ /dev/null @@ -1,416 +0,0 @@ -// -// 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 - -@interface GMAlbumsViewController() - -@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 diff --git a/GMImagePicker/GMEmptyFolder@1x.png b/GMImagePicker/GMEmptyFolder@1x.png deleted file mode 100644 index d2789c724a8eafd3e914c3e7fb64fc7185b6b252..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2932 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 diff --git a/GMImagePicker/GMEmptyFolder@2x.png b/GMImagePicker/GMEmptyFolder@2x.png deleted file mode 100644 index 58848ae156213b25e444a3b1cecb0d94f757b959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3077 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 -#include - - -@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 diff --git a/GMImagePicker/GMGridViewCell.m b/GMImagePicker/GMGridViewCell.m deleted file mode 100644 index 9bbc4c08..00000000 --- a/GMImagePicker/GMGridViewCell.m +++ /dev/null @@ -1,176 +0,0 @@ -// -// 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 diff --git a/GMImagePicker/GMGridViewController.h b/GMImagePicker/GMGridViewController.h deleted file mode 100644 index f3941e46..00000000 --- a/GMImagePicker/GMGridViewController.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// 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 -#include - - -@interface GMGridViewController : UICollectionViewController - -@property (strong,nonatomic) PHFetchResult *assetsFetchResults; - --(id)initWithPicker:(GMImagePickerController *)picker; - -@end diff --git a/GMImagePicker/GMGridViewController.m b/GMImagePicker/GMGridViewController.m deleted file mode 100644 index 3b33baa4..00000000 --- a/GMImagePicker/GMGridViewController.m +++ /dev/null @@ -1,611 +0,0 @@ -// -// 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 - - -//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 () - -@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 diff --git a/GMImagePicker/GMImagePicker.h b/GMImagePicker/GMImagePicker.h deleted file mode 100644 index 976715e6..00000000 --- a/GMImagePicker/GMImagePicker.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// GMImagePicker.h -// GMImagePicker -// -// Created by Shadowfacts on 1/14/19. -// Copyright © 2019 Shadowfacts. All rights reserved. -// - -#import - -//! 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 - - -#import -#import -#import -#import -#import diff --git a/GMImagePicker/GMImagePickerController.h b/GMImagePicker/GMImagePickerController.h deleted file mode 100644 index b5c1ae97..00000000 --- a/GMImagePicker/GMImagePickerController.h +++ /dev/null @@ -1,332 +0,0 @@ -// -// GMImagePickerController.h -// GMPhotoPicker -// -// Created by Guillermo Muntaner Perelló on 19/09/14. -// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved. -// - -#import -#import - - -//This is the default image picker size! -//static CGSize const kPopoverContentSize = {320, 480}; -//However, the iPad is 1024x768 so it can allow popups up to 768! -static CGSize const kPopoverContentSize = {480, 720}; - - -@protocol GMImagePickerControllerDelegate; - - -/** - * A controller that allows picking multiple photos and videos from user's photo library. - */ -@interface GMImagePickerController : UIViewController - -/** - * The assets picker’s delegate object. - */ -@property (nonatomic, weak) id delegate; - -/** - * It contains the selected `PHAsset` objects. The order of the objects is the selection order. - * - * You can add assets before presenting the picker to show the user some preselected assets. - */ -@property (nonatomic, strong) NSMutableArray *selectedAssets; - - -/** UI Customizations **/ - -/** - * Determines which smart collections are displayed (int array of enum: PHAssetCollectionSubtypeSmartAlbum) - * The default smart collections are: - * - Favorites - * - RecentlyAdded - * - Videos - * - SlomoVideos - * - Timelapses - * - Bursts - * - Panoramas - */ -@property (nonatomic, strong) NSArray* customSmartCollections; - -/** - * Determines which media types are allowed (int array of enum: PHAssetMediaType) - * This defaults to all media types (view, audio and images) - * This can override customSmartCollections behavior (ie, remove video-only smart collections) - */ -@property (nonatomic, strong) NSArray* mediaTypes; - -/** - * If set, it displays a this string instead of the localised default of "Done" on the done button. Note also that this - * is not used when a single selection is active since the selection of the chosen photo closes the VC thus rendering - * the button pointless. - */ -@property (nonatomic) NSString* customDoneButtonTitle; - -/** - * If set, it displays this string instead of the localised default of "Cancel" on the cancel button - */ -@property (nonatomic) NSString* customCancelButtonTitle; - -/** - * If set, it displays a prompt in the navigation bar - */ -@property (nonatomic) NSString* customNavigationBarPrompt; - -/** - * Determines whether or not a toolbar with info about user selection is shown. - * The InfoToolbar is visible by default. - */ -@property (nonatomic) BOOL displaySelectionInfoToolbar; - -/** - * Determines whether or not the number of assets is shown in the Album list. - * The number of assets is visible by default. - */ -@property (nonatomic, assign) BOOL displayAlbumsNumberOfAssets; - -/** - * Automatically disables the "Done" button if nothing is selected. Defaults to YES. - */ -@property (nonatomic, assign) BOOL autoDisableDoneButton; - -/** - * Use the picker either for miltiple image selections, or just a single selection. In the case of a single selection - * the VC is closed on selection so the Done button is neither displayed or used. Default is YES. - */ -@property (nonatomic, assign) BOOL allowsMultipleSelection; - -/** - * In the case where allowsMultipleSelection = NO, set this to YES to have the user confirm their selection. Default is NO. - */ -@property (nonatomic, assign) BOOL confirmSingleSelection; - -/** - * If set, it displays this string (if confirmSingleSelection = YES) instead of the localised default. - */ -@property (nonatomic) NSString *confirmSingleSelectionPrompt; - -/** - * True to always show the toolbar, with a camera button allowing new photos to be taken. False to auto show/hide the - * toolbar, and have no camera button. Default is false. If true, this renders displaySelectionInfoToolbar a no-op. - */ -@property (nonatomic, assign) BOOL showCameraButton; - -/** - * True to auto select the image(s) taken with the camera if showCameraButton = YES. In the case of allowsMultipleSelection = YES, - * this will trigger the selection handler too. - */ -@property (nonatomic, assign) BOOL autoSelectCameraImages; - -/** - * If set, the user is allowed to edit captured still images - */ -@property (nonatomic, assign) BOOL allowsEditingCameraImages; - -/** - * Grid customizations: - * - * - colsInPortrait: Number of columns in portrait (3 by default) - * - colsInLandscape: Number of columns in landscape (5 by default) - * - minimumInteritemSpacing: Horizontal and vertical minimum space between grid cells (2.0 by default) - */ -@property (nonatomic) NSInteger colsInPortrait; -@property (nonatomic) NSInteger colsInLandscape; -@property (nonatomic) double minimumInteritemSpacing; - -/** - * UI customizations: - * - * - pickerBackgroundColor: The colour for all backgrounds; behind the table and cells. Defaults to [UIColor whiteColor] - * - pickerTextColor: The color for text in the views. This needs to work with pickerBackgroundColor! Default of darkTextColor - * - toolbarBackgroundColor: The background color of the toolbar. Defaults to nil. - * - toolbarBarTintColor: The color for the background tint of the toolbar. Defaults to nil. - * - toolbarTextColor: The color of the text on the toolbar - * - toolbarTintColor: The tint colour used for any buttons on the toolbar - * - navigationBarBackgroundColor: The background of the navigation bar. Defaults to nil. - * - navigationBarBarTintColor: The color for the background tint of the navigation bar. Defaults to nil. - * - navigationBarTextColor: The color for the text in the navigation bar. Defaults to [UIColor darkTextColor] - * - navigationBarTintColor: The tint color used for any buttons on the navigation Bar - * - pickerFontName: The font to use everywhere. Defaults to HelveticaNeue. It is advised if you set this to check, and possibly set, appropriately the custom font sizes. For font information, check http://www.iosfonts.com/ - * - pickerFontName: The font to use everywhere. Defaults to HelveticaNeue-Bold. It is advised if you set this to check, and possibly set, appropriately the custom font sizes. - * - pickerFontNormalSize: The size of the custom font used in most places. Defaults to 14.0f - * - pickerFontHeaderSize: The size of the custom font for album names. Defaults to 17.0f - * - pickerStatusBarsStyle: On iPhones this will matter if custom navigation bar colours are being used. Defaults to UIStatusBarStyleDefault - * - useCustomFontForNavigationBar: True to use the custom font (or it's default) in the navigation bar, false to leave to iOS Defaults. - * - arrangeSmartCollectionsFirst: True will put the users smart collections above their albums, false will set it opposite. Default is NO. - */ -@property (nonatomic, strong) UIColor *pickerBackgroundColor; -@property (nonatomic, strong) UIColor *pickerTextColor; -@property (nonatomic, strong) UIColor *toolbarBackgroundColor; -@property (nonatomic, strong) UIColor *toolbarBarTintColor; -@property (nonatomic, strong) UIColor *toolbarTextColor; -@property (nonatomic, strong) UIColor *toolbarTintColor; -@property (nonatomic, strong) UIColor *navigationBarBackgroundColor; -@property (nonatomic, strong) UIColor *navigationBarBarTintColor; -@property (nonatomic, strong) UIColor *navigationBarTextColor; -@property (nonatomic, strong) UIColor *navigationBarTintColor; -@property (nonatomic, strong) NSString *pickerFontName; -@property (nonatomic, strong) NSString *pickerBoldFontName; -@property (nonatomic) CGFloat pickerFontNormalSize; -@property (nonatomic) CGFloat pickerFontHeaderSize; -@property (nonatomic) UIStatusBarStyle pickerStatusBarStyle; -@property (nonatomic) BOOL useCustomFontForNavigationBar; -@property (nonatomic) BOOL arrangeSmartCollectionsFirst; - -/** - * A reference to the navigation controller used to manage the whole picking process - */ -@property (nonatomic, strong) UINavigationController *navigationController; - -/** - * Managing Asset Selection - */ -- (void)selectAsset:(PHAsset *)asset; -- (void)deselectAsset:(PHAsset *)asset; - -/** - * User finish Actions - */ -- (void)dismiss:(id)sender; -- (void)finishPickingAssets:(id)sender; - -@end - - - -@protocol GMImagePickerControllerDelegate - -/** - * @name Closing the Picker - */ - -/** - * Tells the delegate that the user finish picking photos or videos. - * @param picker The controller object managing the assets picker interface. - * @param assets An array containing picked PHAssets objects. - */ - -- (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets; - - -@optional - -/** - * Tells the delegate that the user cancelled the pick operation. - * @param picker The controller object managing the assets picker interface. - */ -- (void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker; - - -/** - * @name Enabling Assets - */ - -/** - * Ask the delegate if the specified asset should be shown. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset to be shown. - * - * @return `YES` if the asset should be shown or `NO` if it should not. - */ - -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset; - -/** - * Ask the delegate if the specified asset should be enabled for selection. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset to be enabled. - * - * @return `YES` if the asset should be enabled or `NO` if it should not. - */ -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldEnableAsset:(PHAsset *)asset; - - -/** - * @name Managing the Selected Assets - */ - -/** - * Asks the delegate if the specified asset should be selected. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset to be selected. - * - * @return `YES` if the asset should be selected or `NO` if it should not. - * - */ -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldSelectAsset:(PHAsset *)asset; - -/** - * Tells the delegate that the asset was selected. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset that was selected. - * - */ -- (void)assetsPickerController:(GMImagePickerController *)picker didSelectAsset:(PHAsset *)asset; - -/** - * Asks the delegate if the specified asset should be deselected. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset to be deselected. - * - * @return `YES` if the asset should be deselected or `NO` if it should not. - * - */ -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldDeselectAsset:(PHAsset *)asset; - -/** - * Tells the delegate that the item at the specified path was deselected. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset that was deselected. - * - */ -- (void)assetsPickerController:(GMImagePickerController *)picker didDeselectAsset:(PHAsset *)asset; - - - -/** - * @name Managing Asset Highlighting - */ - -/** - * Asks the delegate if the specified asset should be highlighted. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset to be highlighted. - * - * @return `YES` if the asset should be highlighted or `NO` if it should not. - */ -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldHighlightAsset:(PHAsset *)asset; - -/** - * Tells the delegate that asset was highlighted. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset that was highlighted. - * - */ -- (void)assetsPickerController:(GMImagePickerController *)picker didHighlightAsset:(PHAsset *)asset; - - -/** - * Tells the delegate that the highlight was removed from the asset. - * - * @param picker The controller object managing the assets picker interface. - * @param asset The asset that had its highlight removed. - * - */ -- (void)assetsPickerController:(GMImagePickerController *)picker didUnhighlightAsset:(PHAsset *)asset; - - - - -@end diff --git a/GMImagePicker/GMImagePickerController.m b/GMImagePicker/GMImagePickerController.m deleted file mode 100644 index f1836abf..00000000 --- a/GMImagePicker/GMImagePickerController.m +++ /dev/null @@ -1,388 +0,0 @@ -// -// GMImagePickerController.m -// GMPhotoPicker -// -// Created by Guillermo Muntaner Perelló on 19/09/14. -// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved. -// - -#import -#import "GMImagePickerController.h" -#import "GMAlbumsViewController.h" -#import - -@interface GMImagePickerController () - -@end - -@implementation GMImagePickerController - -- (id)init -{ - if (self = [super init]) { - _selectedAssets = [[NSMutableArray alloc] init]; - - // Default values: - _displaySelectionInfoToolbar = YES; - _displayAlbumsNumberOfAssets = YES; - _autoDisableDoneButton = YES; - _allowsMultipleSelection = YES; - _confirmSingleSelection = NO; - _showCameraButton = NO; - - // Grid configuration: - _colsInPortrait = 3; - _colsInLandscape = 5; - _minimumInteritemSpacing = 2.0; - - // Sample of how to select the collections you want to display: - _customSmartCollections = @[@(PHAssetCollectionSubtypeSmartAlbumFavorites), - @(PHAssetCollectionSubtypeSmartAlbumRecentlyAdded), - @(PHAssetCollectionSubtypeSmartAlbumVideos), - @(PHAssetCollectionSubtypeSmartAlbumSlomoVideos), - @(PHAssetCollectionSubtypeSmartAlbumTimelapses), - @(PHAssetCollectionSubtypeSmartAlbumBursts), - @(PHAssetCollectionSubtypeSmartAlbumPanoramas)]; - // If you don't want to show smart collections, just put _customSmartCollections to nil; - //_customSmartCollections=nil; - - // Which media types will display - _mediaTypes = @[@(PHAssetMediaTypeAudio), - @(PHAssetMediaTypeVideo), - @(PHAssetMediaTypeImage)]; - - self.preferredContentSize = kPopoverContentSize; - - // UI Customisation - _pickerBackgroundColor = [UIColor whiteColor]; - _pickerTextColor = [UIColor darkTextColor]; - _pickerFontName = @"HelveticaNeue"; - _pickerBoldFontName = @"HelveticaNeue-Bold"; - _pickerFontNormalSize = 14.0f; - _pickerFontHeaderSize = 17.0f; - - _navigationBarBackgroundColor = [UIColor whiteColor]; - _navigationBarTextColor = [UIColor darkTextColor]; - _navigationBarTintColor = [UIColor darkTextColor]; - - _toolbarBarTintColor = [UIColor whiteColor]; - _toolbarTextColor = [UIColor darkTextColor]; - _toolbarTintColor = [UIColor darkTextColor]; - - _pickerStatusBarStyle = UIStatusBarStyleDefault; - - [self setupNavigationController]; - } - return self; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - // Ensure nav and toolbar customisations are set. Defaults are in place, but the user may have changed them - self.view.backgroundColor = _pickerBackgroundColor; - - _navigationController.toolbar.translucent = YES; - _navigationController.toolbar.barTintColor = _toolbarBarTintColor; - _navigationController.toolbar.tintColor = _toolbarTintColor; - - _navigationController.navigationBar.backgroundColor = _navigationBarBackgroundColor; - _navigationController.navigationBar.tintColor = _navigationBarTintColor; - NSDictionary *attributes; - if (_useCustomFontForNavigationBar) { - attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor, - NSFontAttributeName : [UIFont fontWithName:_pickerBoldFontName size:_pickerFontHeaderSize]}; - } else { - attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor}; - } - _navigationController.navigationBar.titleTextAttributes = attributes; - - [self updateToolbar]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle { - return _pickerStatusBarStyle; -} - - -#pragma mark - Setup Navigation Controller - -- (void)setupNavigationController -{ - GMAlbumsViewController *albumsViewController = [[GMAlbumsViewController alloc] init]; - _navigationController = [[UINavigationController alloc] initWithRootViewController:albumsViewController]; - _navigationController.delegate = self; - [_navigationController.navigationBar setTranslucent:NO]; - [_navigationController willMoveToParentViewController:self]; - [_navigationController.view setFrame:self.view.frame]; - [self.view addSubview:_navigationController.view]; - [self addConstraintsToChildViewControllersView:_navigationController.view]; - [self addChildViewController:_navigationController]; - [_navigationController didMoveToParentViewController:self]; -} - -- (void)addConstraintsToChildViewControllersView:(UIView *)view { - view.translatesAutoresizingMaskIntoConstraints = NO; - NSArray * hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(view)]; - NSLayoutConstraint * topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; - NSLayoutConstraint * bottomConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; - [view.superview addConstraints:@[topConstraint,bottomConstraint]]; - [view.superview addConstraints:hConstraints]; -} - - -#pragma mark - UIAlertViewDelegate - --(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex -{ - if (buttonIndex == 1) { - // Only if OK was pressed do we want to completge the selection - [self finishPickingAssets:self]; - } -} - - -#pragma mark - Select / Deselect Asset - -- (void)selectAsset:(PHAsset *)asset -{ - [self.selectedAssets insertObject:asset atIndex:self.selectedAssets.count]; - [self updateDoneButton]; - - if (!self.allowsMultipleSelection) { - if (self.confirmSingleSelection) { - NSString *message = self.confirmSingleSelectionPrompt ? self.confirmSingleSelectionPrompt : [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.confirm.message", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Do you want to select the image you tapped on?")]; - - [[[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.confirm.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Are You Sure?")] - message:message - delegate:self - cancelButtonTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.no", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"No")] - otherButtonTitles:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.yes", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Yes")], nil] show]; - } else { - [self finishPickingAssets:self]; - } - } else if (self.displaySelectionInfoToolbar || self.showCameraButton) { - [self updateToolbar]; - } -} - -- (void)deselectAsset:(PHAsset *)asset -{ - [self.selectedAssets removeObjectAtIndex:[self.selectedAssets indexOfObject:asset]]; - if (self.selectedAssets.count == 0) { - [self updateDoneButton]; - } - - if (self.displaySelectionInfoToolbar || self.showCameraButton) { - [self updateToolbar]; - } -} - -- (void)updateDoneButton -{ - if (!self.allowsMultipleSelection) { - return; - } - - UINavigationController *nav = (UINavigationController *)self.childViewControllers[0]; - for (UIViewController *viewController in nav.viewControllers) { - viewController.navigationItem.rightBarButtonItem.enabled = (self.autoDisableDoneButton ? self.selectedAssets.count > 0 : TRUE); - } -} - -- (void)updateToolbar -{ - if (!self.allowsMultipleSelection && !self.showCameraButton) { - return; - } - - UINavigationController *nav = (UINavigationController *)self.childViewControllers[0]; - for (UIViewController *viewController in nav.viewControllers) { - NSUInteger index = 1; - if (_showCameraButton) { - index++; - } - [[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateNormal]; - [[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateDisabled]; - [[viewController.toolbarItems objectAtIndex:index] setTitle:[self toolbarTitle]]; - [viewController.navigationController setToolbarHidden:(self.selectedAssets.count == 0 && !self.showCameraButton) animated:YES]; - } -} - - -#pragma mark - User finish Actions - -- (void)dismiss:(id)sender -{ - if ([self.delegate respondsToSelector:@selector(assetsPickerControllerDidCancel:)]) { - [self.delegate assetsPickerControllerDidCancel:self]; - } - - [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; -} - - -- (void)finishPickingAssets:(id)sender -{ - if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingAssets:)]) { - [self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedAssets]; - } -} - - -#pragma mark - Toolbar Title - -- (NSPredicate *)predicateOfAssetType:(PHAssetMediaType)type -{ - return [NSPredicate predicateWithBlock:^BOOL(PHAsset *asset, NSDictionary *bindings) { - return (asset.mediaType == type); - }]; -} - -- (NSString *)toolbarTitle -{ - if (self.selectedAssets.count == 0) { - return nil; - } - - NSPredicate *photoPredicate = [self predicateOfAssetType:PHAssetMediaTypeImage]; - NSPredicate *videoPredicate = [self predicateOfAssetType:PHAssetMediaTypeVideo]; - - NSInteger nImages = [self.selectedAssets filteredArrayUsingPredicate:photoPredicate].count; - NSInteger nVideos = [self.selectedAssets filteredArrayUsingPredicate:videoPredicate].count; - - if (nImages > 0 && nVideos > 0) { - return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-items", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Items Selected" ), @(nImages + nVideos)]; - } else if (nImages > 1) { - return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-photos", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Photos Selected"), @(nImages)]; - } else if (nImages == 1) { - return NSLocalizedStringFromTableInBundle(@"picker.selection.single-photo", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"1 Photo Selected" ); - } else if (nVideos > 1) { - return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-videos", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"%@ Videos Selected"), @(nVideos)]; - } else if (nVideos == 1) { - return NSLocalizedStringFromTableInBundle(@"picker.selection.single-video", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"1 Video Selected"); - } else { - return nil; - } -} - - -#pragma mark - Toolbar Items - -- (void)cameraButtonPressed:(UIBarButtonItem *)button -{ - if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Camera!" - message:@"Sorry, this device does not have a camera." - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; - - return; - } - - // This allows the selection of the image taken to be better seen if the user is not already in that VC - if (self.autoSelectCameraImages && [self.navigationController.topViewController isKindOfClass:[GMAlbumsViewController class]]) { - [((GMAlbumsViewController *)self.navigationController.topViewController) selectAllAlbumsCell]; - } - - UIImagePickerController *picker = [[UIImagePickerController alloc] init]; - picker.sourceType = UIImagePickerControllerSourceTypeCamera; - picker.mediaTypes = @[(NSString *)kUTTypeImage]; - picker.allowsEditing = self.allowsEditingCameraImages; - picker.delegate = self; - picker.modalPresentationStyle = UIModalPresentationPopover; - - UIPopoverPresentationController *popPC = picker.popoverPresentationController; - popPC.permittedArrowDirections = UIPopoverArrowDirectionAny; - popPC.barButtonItem = button; - - [self showViewController:picker sender:button]; -} - -- (NSDictionary *)toolbarTitleTextAttributes { - return @{NSForegroundColorAttributeName : _toolbarTextColor, - NSFontAttributeName : [UIFont fontWithName:_pickerFontName size:_pickerFontHeaderSize]}; -} - -- (UIBarButtonItem *)titleButtonItem -{ - UIBarButtonItem *title = [[UIBarButtonItem alloc] initWithTitle:self.toolbarTitle - style:UIBarButtonItemStylePlain - target:nil - action:nil]; - - NSDictionary *attributes = [self toolbarTitleTextAttributes]; - [title setTitleTextAttributes:attributes forState:UIControlStateNormal]; - [title setTitleTextAttributes:attributes forState:UIControlStateDisabled]; - [title setEnabled:NO]; - - return title; -} - -- (UIBarButtonItem *)spaceButtonItem -{ - return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; -} - -- (UIBarButtonItem *)cameraButtonItem -{ - return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@selector(cameraButtonPressed:)]; -} - -- (NSArray *)toolbarItems -{ - UIBarButtonItem *camera = [self cameraButtonItem]; - UIBarButtonItem *title = [self titleButtonItem]; - UIBarButtonItem *space = [self spaceButtonItem]; - - NSMutableArray *items = [[NSMutableArray alloc] init]; - if (_showCameraButton) { - [items addObject:camera]; - } - [items addObject:space]; - [items addObject:title]; - [items addObject:space]; - - return [NSArray arrayWithArray:items]; -} - - -#pragma mark - Camera Delegate - -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info -{ - [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil]; - - NSString *mediaType = info[UIImagePickerControllerMediaType]; - if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { - UIImage *image = info[UIImagePickerControllerEditedImage] ? : info[UIImagePickerControllerOriginalImage]; - UIImageWriteToSavedPhotosAlbum(image, - self, - @selector(image:finishedSavingWithError:contextInfo:), - nil); - } -} - --(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker -{ - [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil]; -} - --(void)image:(UIImage *)image finishedSavingWithError:(NSError *)error contextInfo:(void *)contextInfo -{ - if (error) { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Image Not Saved" - message:@"Sorry, unable to save the new image!" - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil]; - [alert show]; - } - - // Note: The image view will auto refresh as the photo's are being observed in the other VCs -} - -@end diff --git a/GMImagePicker/GMSelected.png b/GMImagePicker/GMSelected.png deleted file mode 100755 index b251b6cd3d7866b3f60ffbb9c0581eb47c9f74a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2141 zcmbVNX;f2Z8ct;?2GJ>?2r`gHkVQf^5|TiGkgx;^(1Zf6&2}Lf$U<@@0TC>MvRG-w z0#&GJRjMG1g9{P}ivo%)l?o^oup*Q>fEEoPo+ERk;>-`HKjxl$@ArN0ectEY&&`Pl z3o?V-z+o_$88eu{hE{w1V`8j-S8X6ei!~S!19D`spo*seU^JnO4Mh%ap&nV?rMn~(uTpjebsWFARYh_d>~FNr6Qj-Xpso9kcx~Z zurMq+9T16wQxpIvB`i{q5-%VNk-k0%Z#4xXkN_YLp_U{_l@v7<`IeUg?e$_b67d!S z##52+oQh#ZAm}m$fFQWzQ34DWiy#u+u^t2>5$lG)VX!zf2D*tTtOtcaq~JUe?=K`| zO(Be>uo;2xZ9yk0QUro>3L33asoYg~cbOs%jU|)GdJY^81tCz%WGTo~qom5sD+~-k zDNu;zpjakF=oxu@*n&OdvVs4& z@lmugGFc9w*?>~ES0RAv5xaRs8Oq&X8`1+IZz$mkF;o;@0z)R)D*>b+lR-s7U)+Ua zA%*4<5a3BBlrR4;JR7wBEQ3_u}H0)&AInFR4R!xZsHJn&?Y=pGn6AprlsB7dhV z{C~2BhMYm`+vC65Wkm$_pnm(I`Juyy@d2dJ%qXDYuq%fz!eEAZOa?7d{qSz|SD`xr zOW%cgdyl$}(QpS8>e$9_=-l{0;r6XR;G15JA6(8T-HF=xsASub#P(@gLE}cNqHMF_ zHAA(IC&a0JZakODM|GH=LoI(Rk#Ih<%`$?YLp3)3SUpcH8fHrm2WrR1C(=f#ld3oS zCrh@`Ugfzaw!h(IWR9-R8IDVC_J3$j`jlbyc?dW0yvg^G84nGM4+JY59D8SHrMvg- zYhYfA+T@&jtk$jJYF(YBovp2AuOkGYJ4!rJm=;j zzl7J(x_bWPrmfS_Uq78L&=lB4dR{f`QR$aU4twUpxJ-4=x1v}ik z&_6Ze^+uObHyN^qJ=NXlRKy;uxiGfKS-|)A_di1}cU&*M>(cz3?zcYC^OLB|w#PlE z3O$^{HvhaZ;93LhLc$$qq!(e6f`6#bQkDu^xQm(PemTW`4;l+W0fr5^vUElsK@7iUPae)yin} z(!JCCh)M?XyBB|o((K!}e8;{swW{4zn*qj8-#;>Rn82 zs*Sa*JYKCzgzul2F6|=bj~MLuBfWmZ+ES}^yK?4E{KDB&y+F}w6wbTw7j9l9jmy!* z^w$DS4L8_X-WX#r*_7YX-1~dQXu>H(DE^9mAlT?1mtu?*xqA{{}3cQgu zcAT4yc(A3zy>3v5+nw63D<7X5Z}cqcNW6Z1tP%(e3OW^-Y*62l!6^fM*EwEs3Nv$S z?dzU(~d}crxqspI~@o%uac-{a2 diff --git a/GMImagePicker/GMSelected@2x.png b/GMImagePicker/GMSelected@2x.png deleted file mode 100755 index 5a8f6f8cc5eed008a21ec03dcfa710bdc05ef579..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3543 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 diff --git a/GMImagePicker/GMVideoIcon.png b/GMImagePicker/GMVideoIcon.png deleted file mode 100644 index 865e68d2eeb4ba5c4c6334690d6dfc76eb75b46f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^{6NgX!2%?EZsk@2sZ>uF$B+ufbP0 Hl+XkKIl?&q diff --git a/GMImagePicker/GMVideoIcon@2x.png b/GMImagePicker/GMVideoIcon@2x.png deleted file mode 100644 index 4ed325c52650489c19833dae4c126b763790342b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^azHG=!3HE5tuUp7)A^qT+89bj(ZoRzAZ^qjCH8Ysw z{9adaa=lSL^xacv%HikyA5;GGfR3P#)(5(iqO546laAhSI};UT9Vm$q?F r!ulc=DLbof!C - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/GMImagePicker/ca.lproj/GMImagePicker.strings b/GMImagePicker/ca.lproj/GMImagePicker.strings deleted file mode 100644 index b33a4deb834414e2bedd550f67ea14e00c0fe266..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1682 zcmcIk!A`b(-|w`9^lM~*Ay)Kd z#^*EBB_F?(eM5u+^ZB?SMvX{clm`B-Kerz*7ZfCw{YpD*l@ zOxaDz5txed%*rjt+*+8LYqaaNbUm%8naB0;!cIunPtSH?<+k}Yab|QHTgWcX(or|- zXVxX&|6ncmq{wZvmd>uR-TORpD|&lK=O*J+<-bitN^Wz`U_K41uksYMLRUA#m{VMy zT8U9bPsvcVEHhLc-5jeo|C$xccGHSwTHQA(V|_!TP{@q`1OVaf! za^KwgCG8%Bg>1?EX8(+y?qdVPq=*f8FA`b=PqwM<+Ai*~;d9Hq+u|PSqQea$8>NFM dT61rNHqT<|S2}qt!qGgOJflPhT?Cl3`k3|ltX$|@R^${HL=K3XFgoRWz?E)4P;;N& zOT5|9f3h< YOZz=-xu-nEhjNoClmCVNnEi(T0dxKkm;e9( diff --git a/GMImagePicker/en.lproj/GMImagePicker.strings b/GMImagePicker/en.lproj/GMImagePicker.strings deleted file mode 100644 index b1836b63626409332069ee6324e8b58a7c264664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2346 zcmcJRTWi}u5QXPCzd{%SZ9q1CE~Pb2DS^fbbtvS~vE|exmW?E*NoI@U_8XT$i{d%VZS5- z1<&%ZV1Fn#ujl>$W|wwhGpyfZ@46P%km0F)0r>*lgs*e<19svta^qtdeHwi~mFvDSuSd}*LiNoZDRTpua(=kV5)AYY_BujjvF%l zKwiCP|CjX~PAPR(X57cp^V@~?nw$6KnB08yLT=m8LT+8W)V%Uusv4U+q@ermTvXcZ zq!$NzF%o^De)D`Z7iY*a*2sQPMbZpe3|wbtbVFofq#0Ot?`|gfi*D>#Rk|@fkRVZY zWi>tnCP^0_H(>Sb%D!`bWp~5WxUxR;BII@`pC$b)#QoUp!=OjZQYPyZtD1n-$q^x+ z;6=S&_2lPWImaRw7jfEeDvtuZw!*yg_?l=%spoq4DWx7l;gsiu{ zZBI{c`}r|I9~Op~VT%PKyz$h>06o~)VNKmDwJ}`UXV_D-;y2~@of?;R^>i>tMq6rZ zHX~j0Z&#wv7|X^Ll1%3FUy2U|1MkNKJEZlG44RGn->AYxYrM zIHsaJvSQ0~9!fbimRn;mzdOYXxtAWFAK|VPyN+>L%G!qUnYBts{{y4UKoPr+agW2v zW|5zwdvzu+*-7Q`Z+%XPXUmtB_gEQJY?7^DuML$lcTQBwSrwJ?^%JgJ)+T#NYm=AP zJP9eMY;L%4DT$v`@pGt=8`2rYBKt4qFEVpboyx#WZcK{>K_WXYpU!p!$;sC-XbNA4 z?>wSL-jVD>-IPAYxaY5QXR3rwD=G2vK@n3Kc6Nv??gQ_b0VdO~u5%b6%qDH@itU36UyVM6x@x zGw00No%#N1OH(}Q%1k0zNGKb2O=(L*eA&qw+dJ01wD8a56U&Nk!uJD93%`pDWG)AM ziShYly65jphm#SWjmhN89oZXTDDtB@8=i@}%%YAVP7Tr)mXV||C?XDP>hNGbqe@Sf zM82|Cwh4Q!G%?K2)5XO(9s2St1KE;&1VBH}U}T0Njw%G3yB{Rc~QzODy$w z6c^6rH?+-aS#zJ7BQO=&%#NGIOgWK)8KPTakYn~avdyT9#uZI%^jO}w7rp)KbKVtl zS6LT*9@uq^!y}wuP($zhKR9c*6HY_NRMDLM zxU%Kft5HtXYjSjYmN_~tPvzL5YMpehI(@PAJ(bX}bE8s=mBt07xB!cu&<=IH#%8}k z<3%?4$|_CF9N5z>o+ulgoVM5hW)c6!28K}{8_**m7JYO30++6gcq|)8z@;lIL%HR@qxmrKksT~Ud diff --git a/GMImagePicker/it.lproj/GMImagePicker.strings b/GMImagePicker/it.lproj/GMImagePicker.strings deleted file mode 100644 index f6946b3287fcb4fff69e401a4d4a11ebf6d1fd8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1656 zcmcIk%TB^T6g_Lcq9M8=DTK8#vB5`35C|9-?g|tnLLqH&;m51z&a_Ua86=Gu0?gdG zk8{s`e0_G%f`=YL#F!((hPM_vXrPHb)|9>NpOE(}dT%g>M7~wJQO&4}-0E1wd*X|U+m1DKWZ)5V$oxDksK4Y{ zaAv$qjv3E&u^BtVzylu9$Cln#^f#vGf>#^iC2knG&$tn_Q=X?h35L0nEhuqUCoSm} zwb|ymtXWC%;j4_aXM;`0)KRw7idE(wqifj*t)$kv>Mp!vyi!A|etyLJlJ7F+E-n;T z%RI6hZ?yw-uFuR!s{J2$WkL$yWxP^X^X5jouJmh{Hip~3^+HNqF}qLqfnn8PlxV-R zS)E<6FkGG!pJB?zXXI`McNGQ$<|`Y`nmJ^tK diff --git a/GMImagePicker/pt.lproj/GMImagePicker.strings b/GMImagePicker/pt.lproj/GMImagePicker.strings deleted file mode 100644 index 2f3caedc52aff22f63d39f060d64a1ca00c41e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1586 zcmb_c+fKqj5IxU)1!?q!q!6BsiAD_}A`okQUrIqDx7flj`UU=g@BV^%X18^=q{N^+ zv^zU9JLk-0_v^ET2I{zi!4feN%(-izg&G2^F{AF8+7~owH`r1$<(hF_QPZSdPlgy_ zLtAET0VAFB3rf)qV+A;4{2EaN``UUZ2jY(NQ~NLnxZT0&SMaZsu`sG*%q%CAH>^O$ zy=0hCK9rZ9)BHQQLl+B1pEBOit7^Ftr??<;m$(VNQ_2w~;SgKhn40R|q@G{VnMIz< zjD3_Cq0PueR&EZ{p)BaNB+`o5Rm_HXqRrWl&28<)_Sv;@VymJV#`)|S_RQ>I`bZz? zsAO_%rg8_0*gjL~-4-97&7bl%ty;hTaZ1Tx$yst;)ygRwL7U{gnsUAkcQgLWT@^2_ z)G;|}jmn&~K4m7C%tc-v%|-fFJ?k0s7}?yLl;m7kIu}wS9x~I4e$Q&m`N>T;{Kt5= zH&SX-<`U(4c?E=*ylL(>{>~@;IU8DT#o3V4olqllBk!NSj05y=!x!=uZ(Ji1 LSRequiresIPhoneOS + NSMicrophoneUsageDescription + Post videos from the camera. NSCameraUsageDescription - Post photos from the camera. + Post photos and videos from the camera. NSPhotoLibraryAddUsageDescription Save photos directly from other people's posts. NSPhotoLibraryUsageDescription diff --git a/Tusker/Screens/Compose/Asset Picker/AlbumAssetCollectionViewController.swift b/Tusker/Screens/Compose/Asset Picker/AlbumAssetCollectionViewController.swift new file mode 100644 index 00000000..751a56b9 --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AlbumAssetCollectionViewController.swift @@ -0,0 +1,30 @@ +// +// AlbumAssetCollectionViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/4/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +class AlbumAssetCollectionViewController: AssetCollectionViewController { + + let collection: PHAssetCollection + + init(collection: PHAssetCollection) { + self.collection = collection + + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func fetchAssets(with options: PHFetchOptions) -> PHFetchResult { + return PHAsset.fetchAssets(in: collection, options: options) + } + +} diff --git a/Tusker/Screens/Compose/Asset Picker/AssetCollectionViewController.swift b/Tusker/Screens/Compose/Asset Picker/AssetCollectionViewController.swift new file mode 100644 index 00000000..2cd509ca --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AssetCollectionViewController.swift @@ -0,0 +1,186 @@ +// +// AssetCollectionViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +private let reuseIdentifier = "assetCell" +private let cameraReuseIdentifier = "showCameraCell" + +protocol AssetCollectionViewControllerDelegate: class { + func shouldSelectAsset(_ asset: PHAsset) -> Bool + func didSelectAssets(_ assets: [PHAsset]) + func captureFromCamera() +} + +class AssetCollectionViewController: UICollectionViewController { + + weak var delegate: AssetCollectionViewControllerDelegate? + + var flowLayout: UICollectionViewFlowLayout { + return collectionViewLayout as! UICollectionViewFlowLayout + } + + var availableWidth: CGFloat! + var thumbnailSize: CGSize! + + let imageManager = PHCachingImageManager() + var fetchResult: PHFetchResult! + + var selectedAssets: [PHAsset] { + return collectionView.indexPathsForSelectedItems?.map({ (indexPath) in + fetchResult.object(at: indexPath.row - 1) + }) ?? [] + } + + init() { + super.init(collectionViewLayout: UICollectionViewFlowLayout()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) + + collectionView.alwaysBounceVertical = true + + collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier) + collectionView.register(UINib(nibName: "ShowCameraCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: cameraReuseIdentifier) + + let options = PHFetchOptions() + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + fetchResult = fetchAssets(with: options) + + collectionView.allowsMultipleSelection = true + setEditing(true, animated: false) + + updateItemsSelected() + + if let singleFingerPanGesture = collectionView.gestureRecognizers?.first(where: { + $0.name == "multi-select.singleFingerPanGesture" + }), + let interactivePopGesture = navigationController?.interactivePopGestureRecognizer { + singleFingerPanGesture.require(toFail: interactivePopGesture) + } + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + let availableWidth = view.bounds.inset(by: view.safeAreaInsets).width + + if self.availableWidth != availableWidth { + self.availableWidth = availableWidth + + let size = (availableWidth - 8) / 3 + flowLayout.itemSize = CGSize(width: size, height: size) + flowLayout.minimumInteritemSpacing = 4 + flowLayout.minimumLineSpacing = 4 + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let scale = UIScreen.main.scale + let cellSize = flowLayout.itemSize + thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) + } + + open func fetchAssets(with options: PHFetchOptions) -> PHFetchResult { + return PHAsset.fetchAssets(with: options) + } + + func updateItemsSelected() { + let selected = collectionView.indexPathsForSelectedItems?.count ?? 0 + + navigationItem.title = "\(selected) selected" + } + + // MARK: UICollectionViewDataSource + + override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return fetchResult.count + 1 + } + + override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if indexPath.row == 0 { + return collectionView.dequeueReusableCell(withReuseIdentifier: cameraReuseIdentifier, for: indexPath) + } else { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! AssetCollectionViewCell + + let asset = fetchResult.object(at: indexPath.row - 1) + + cell.updateUI(asset: asset) + imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) { (image, _) in + guard let image = image else { return } + DispatchQueue.main.async { + guard cell.assetIdentifier == asset.localIdentifier else { return } + cell.thumbnailImage = image + } + } + + return cell + } + } + + // MARK: UICollectionViewDelegate + + override func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { + return true + } + + override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + if indexPath.row > 0, + let delegate = delegate { + let asset = fetchResult.object(at: indexPath.row - 1) + return delegate.shouldSelectAsset(asset) + } + return true + } + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if indexPath.row == 0 { + collectionView.deselectItem(at: indexPath, animated: false) + delegate?.captureFromCamera() + } else { + updateItemsSelected() + } + } + + override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + updateItemsSelected() + } + + override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + if indexPath.row == 0 { + return nil + } else { + let asset = fetchResult.object(at: indexPath.row - 1) + return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in + return AssetPreviewViewController(asset: asset) + }, actionProvider: nil) + } + } + + // MARK: - Interaction + + @objc func donePressed() { + delegate?.didSelectAssets(selectedAssets) + dismiss(animated: true) + } + +} diff --git a/Tusker/Screens/Compose/Asset Picker/AssetCollectionsListViewController.swift b/Tusker/Screens/Compose/Asset Picker/AssetCollectionsListViewController.swift new file mode 100644 index 00000000..1d351cec --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AssetCollectionsListViewController.swift @@ -0,0 +1,134 @@ +// +// AssetCollectionsListViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +class AssetCollectionsListViewController: UITableViewController { + + weak var assetCollectionDelegate: AssetCollectionViewControllerDelegate? + + var dataSource: DataSource! + + init() { + super.init(style: .plain) + + title = NSLocalizedString("Collections", comment: "asset collections list title") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed)) + + tableView.register(UINib(nibName: "AllPhotosTableViewCell", bundle: .main), forCellReuseIdentifier: "allPhotosCell") + tableView.register(UINib(nibName: "AlbumTableViewCell", bundle: .main), forCellReuseIdentifier: "albumCell") + + dataSource = DataSource(tableView: tableView, cellProvider: { (tableView, indexPath, item) -> UITableViewCell? in + switch item { + case .cameraRoll: + return tableView.dequeueReusableCell(withIdentifier: "allPhotosCell", for: indexPath) + + case let .album(collection): + let cell = tableView.dequeueReusableCell(withIdentifier: "albumCell", for: indexPath) as! AlbumTableViewCell + cell.updateUI(album: collection) + return cell + } + }) + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.system, .albums, .smartAlbums]) + snapshot.appendItems([.cameraRoll], toSection: .system) + + let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil) + var smartAlbumItems = [Item]() + smartAlbums.enumerateObjects { (collection, _, _) in + guard collection.assetCollectionSubtype != .smartAlbumAllHidden && collection.assetCollectionSubtype != .smartAlbumRecentlyAdded else { + return + } + smartAlbumItems.append(.album(collection)) + } + snapshot.appendItems(smartAlbumItems, toSection: .smartAlbums) + + let albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil) + var albumItems = [Item]() + albums.enumerateObjects { (collection, _, _) in + if collection.estimatedAssetCount > 0 { + albumItems.append(.album(collection)) + } + } + snapshot.appendItems(albumItems, toSection: .albums) + + dataSource.apply(snapshot, animatingDifferences: false) + + } + + // MARK: - Table view delegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + switch dataSource.itemIdentifier(for: indexPath) { + case nil: + return + + case .cameraRoll: + let assetCollection = AssetCollectionViewController() + assetCollection.delegate = assetCollectionDelegate + show(assetCollection, sender: self) + + case let .album(collection): + let assetCollection = AlbumAssetCollectionViewController(collection: collection) + assetCollection.delegate = assetCollectionDelegate + show(assetCollection, sender: self) + } + } + + // MARK: - Interaction + + @objc func cancelPressed() { + dismiss(animated: true) + } + +} + +extension AssetCollectionsListViewController { + enum Section { + case system + case albums + case smartAlbums + } + enum Item: Hashable { + case cameraRoll + case album(PHAssetCollection) + + func hash(into hasher: inout Hasher) { + switch self { + case .cameraRoll: + hasher.combine("cameraRoll") + case let .album(collection): + hasher.combine("album") + hasher.combine(collection.localIdentifier) + } + } + } + class DataSource: UITableViewDiffableDataSource { + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + let currentSnapshot = snapshot() + if currentSnapshot.indexOfSection(.albums) == section { + return NSLocalizedString("Albums", comment: "albums section title") + } else if currentSnapshot.indexOfSection(.smartAlbums) == section { + return NSLocalizedString("Smart Albums", comment: "smart albums section title") + } else { + return nil + } + } + } +} diff --git a/Tusker/Screens/Compose/Asset Picker/AssetPickerSheetContainerViewController.swift b/Tusker/Screens/Compose/Asset Picker/AssetPickerSheetContainerViewController.swift new file mode 100644 index 00000000..b36c599c --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AssetPickerSheetContainerViewController.swift @@ -0,0 +1,68 @@ +// +// AssetPickerSheetContainerViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import SheetController +import Photos + +class AssetPickerSheetContainerViewController: SheetContainerViewController { + + let assetPicker = AssetPickerViewController() + + init() { + super.init(content: assetPicker) + + assetPicker.view.translatesAutoresizingMaskIntoConstraints = false + assetPicker.view.layer.masksToBounds = true + + delegate = self + assetPicker.delegate = self + detents = [.bottom, .middle, .top] + + overrideUserInterfaceStyle = .dark + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + assetPicker.view.layer.cornerRadius = view.bounds.width * 0.02 + + super.viewDidLoad() + } + +} + +extension AssetPickerSheetContainerViewController: SheetContainerViewControllerDelegate { + func sheetContainer(_ sheetContainer: SheetContainerViewController, willSnapToDetent detent: Detent) -> Bool { + if detent == .bottom { + dismiss(animated: true) + return false + } + return true + } + func sheetContainerContentScrollView(_ sheetContainer: SheetContainerViewController) -> UIScrollView? { + if let vc = assetPicker.visibleViewController as? UITableViewController { + return vc.tableView + } else if let vc = assetPicker.visibleViewController as? UICollectionViewController { + return vc.collectionView + } + return nil + } + func sheetContainer(_ sheetContainer: SheetContainerViewController, topContentOffsetForScrollView scrollView: UIScrollView) -> CGFloat { + return assetPicker.navigationBar.bounds.height + } +} + +extension AssetPickerSheetContainerViewController: UINavigationControllerDelegate { + func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { + contentScrollViewChanged() +// viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) + } +} diff --git a/Tusker/Screens/Compose/Asset Picker/AssetPickerViewController.swift b/Tusker/Screens/Compose/Asset Picker/AssetPickerViewController.swift new file mode 100644 index 00000000..9b820261 --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AssetPickerViewController.swift @@ -0,0 +1,95 @@ +// +// AssetPickerViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +protocol AssetPickerViewControllerDelegate { + func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool + func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) +} + +class AssetPickerViewController: UINavigationController { + + var assetPickerDelegate: AssetPickerViewControllerDelegate? + + var currentCollectionSelectedAssets: [CompositionAttachment] { + if let vc = visibleViewController as? AssetCollectionViewController { + return vc.selectedAssets.map { .asset($0) } + } else { + return [] + } + } + + init() { + super.init(navigationBarClass: nil, toolbarClass: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let assetCollectionsList = AssetCollectionsListViewController() + assetCollectionsList.assetCollectionDelegate = self + let assetCollection = AssetCollectionViewController() + assetCollection.delegate = self + setViewControllers([assetCollectionsList, assetCollection], animated: false) + } + + func presentImagePicker(animated: Bool) { + let imagePicker = UIImagePickerController() + imagePicker.delegate = self + imagePicker.sourceType = .camera + imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .camera)! + self.present(imagePicker, animated: true) + } + +} + +extension AssetPickerViewController: AssetCollectionViewControllerDelegate { + func shouldSelectAsset(_ asset: PHAsset) -> Bool { + guard let delegate = assetPickerDelegate else { return true } + guard let type = asset.attachmentType else { return false } + return delegate.assetPicker(self, shouldAllowAssetOfType: type) + } + func didSelectAssets(_ assets: [PHAsset]) { + assetPickerDelegate?.assetPicker(self, didSelectAttachments: assets.map { .asset($0) }) + } + func captureFromCamera() { + presentImagePicker(animated: true) + } +} + +extension AssetPickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + let attachment: CompositionAttachment + if let image = info[.originalImage] as? UIImage { + attachment = .image(image) + } else if let url = info[.mediaURL] as? URL { + attachment = .video(url) + } else { + return + } + + if assetPickerDelegate?.assetPicker(self, shouldAllowAssetOfType: attachment.type) ?? true { + assetPickerDelegate?.assetPicker(self, didSelectAttachments: [attachment]) + // dismiss image picker + dismiss(animated: true) { + // dismiss asset picker + self.dismiss(animated: true) + } + } else { + dismiss(animated: false) { + self.presentImagePicker(animated: false) + } + } + } +} diff --git a/Tusker/Screens/Compose/Asset Picker/AssetPreviewViewController.swift b/Tusker/Screens/Compose/Asset Picker/AssetPreviewViewController.swift new file mode 100644 index 00000000..ece1efd5 --- /dev/null +++ b/Tusker/Screens/Compose/Asset Picker/AssetPreviewViewController.swift @@ -0,0 +1,118 @@ +// +// AssetPreviewViewController.swift +// Tusker +// +// Created by Shadowfacts on 1/4/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos +import PhotosUI +import AVKit + +class AssetPreviewViewController: UIViewController { + + let asset: PHAsset + + init(asset: PHAsset) { + self.asset = asset + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .black + + if asset.mediaType == .image { + if asset.mediaSubtypes.contains(.photoLive) { + showLivePhoto() + } else { + showImage() + } + } else if asset.mediaType == .video { + playVideo() + } else { + fatalError("asset mediaType must be image or video") + } + } + + func showImage() { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.contentMode = .scaleAspectFit + view.addSubview(imageView) + NSLayoutConstraint.activate([ + imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + imageView.topAnchor.constraint(equalTo: view.topAnchor), + imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + let options = PHImageRequestOptions() + options.version = .current + options.deliveryMode = .opportunistic + options.resizeMode = .none + options.isNetworkAccessAllowed = true + PHImageManager.default().requestImage(for: asset, targetSize: view.bounds.size, contentMode: .aspectFit, options: options) { (image, _) in + DispatchQueue.main.async { + imageView.image = image + } + } + } + + func showLivePhoto() { + let options = PHLivePhotoRequestOptions() + options.deliveryMode = .opportunistic + options.version = .current + options.isNetworkAccessAllowed = true + PHImageManager.default().requestLivePhoto(for: asset, targetSize: view.bounds.size, contentMode: .aspectFit, options: options) { (livePhoto, _) in + guard let livePhoto = livePhoto else { + fatalError("failed to get live photo") + } + DispatchQueue.main.async { + let livePhotoView = PHLivePhotoView() + livePhotoView.livePhoto = livePhoto + livePhotoView.isMuted = true + livePhotoView.startPlayback(with: .full) + livePhotoView.translatesAutoresizingMaskIntoConstraints = false + livePhotoView.contentMode = .scaleAspectFit + self.view.addSubview(livePhotoView) + NSLayoutConstraint.activate([ + livePhotoView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + livePhotoView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + livePhotoView.topAnchor.constraint(equalTo: self.view.topAnchor), + livePhotoView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) + ]) + } + } + } + + func playVideo() { + let options = PHVideoRequestOptions() + options.deliveryMode = .automatic + options.isNetworkAccessAllowed = true + options.version = .current + PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, _, _) in + guard let avAsset = avAsset else { + fatalError("failed to get AVAsset") + } + DispatchQueue.main.async { + let playerController = AVPlayerViewController() + let item = AVPlayerItem(asset: avAsset) + let player = AVPlayer(playerItem: item) + player.isMuted = true + player.play() + playerController.player = player + self.embedChild(playerController) + } + } + } + +} diff --git a/Tusker/Screens/Compose/ComposeViewController.swift b/Tusker/Screens/Compose/ComposeViewController.swift index c72f4bf9..4caace12 100644 --- a/Tusker/Screens/Compose/ComposeViewController.swift +++ b/Tusker/Screens/Compose/ComposeViewController.swift @@ -9,9 +9,6 @@ import UIKit import Pachyderm import Intents -import Photos -import GMImagePicker -import MobileCoreServices class ComposeViewController: UIViewController { @@ -28,7 +25,7 @@ class ComposeViewController: UIViewController { visibilityChanged() } } - var selectedAssets: [PHAsset] = [] { + var selectedAttachments: [CompositionAttachment] = [] { didSet { updateAttachmentViews() } @@ -303,7 +300,7 @@ class ComposeViewController: UIViewController { case .pleroma: addAttachmentButton.isEnabled = true case .mastodon: - addAttachmentButton.isEnabled = selectedAssets.count <= 4 && selectedAssets.first(where: { $0.mediaType == .video }) == nil + addAttachmentButton.isEnabled = selectedAttachments.count <= 4 && !selectedAttachments.contains(where: { $0.type == .video }) } } @@ -314,10 +311,10 @@ class ComposeViewController: UIViewController { } } - for asset in selectedAssets { + for attachment in selectedAttachments { let mediaView = ComposeMediaView.create() mediaView.delegate = self - mediaView.update(asset: asset) + mediaView.update(attachment: attachment) attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1) updateAddAttachmentButton() } @@ -339,12 +336,11 @@ class ComposeViewController: UIViewController { func saveDraft() { var attachments = [DraftsManager.DraftAttachment]() - for asset in selectedAssets { - let index = attachments.count - let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView - let description = mediaView.descriptionTextView.text! - - attachments.append(DraftsManager.DraftAttachment(assetIdentifier: asset.localIdentifier, description: description)) + for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews + where mediaView.attachment.canSaveToDraft { + let attachment = mediaView.attachment! + let description = mediaView.descriptionTextView.text ?? "" + attachments.append(.init(attachment: attachment, description: description)) } let cw = contentWarningEnabled ? contentWarningTextField.text : nil if let currentDraft = self.currentDraft { @@ -360,13 +356,6 @@ class ComposeViewController: UIViewController { xcbSession?.complete(with: .cancel) } - // MARK: - Navigation - - override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - statusTextView.resignFirstResponder() - super.dismiss(animated: flag, completion: completion) - } - // MARK: - Interaction @objc func showSaveAndClosePrompt() { @@ -448,12 +437,13 @@ class ComposeViewController: UIViewController { } @IBAction func addAttachmentPressed(_ sender: Any) { - let picker = GMImagePickerController() - picker.delegate = self - picker.toolbarTintColor = view.tintColor - picker.navigationBarTintColor = view.tintColor - picker.title = "Choose Attachment" - present(picker, animated: true) + // hide keyboard before showing asset picker, so it doesn't re-appear when asset picker is closed + contentWarningTextField.resignFirstResponder() + statusTextView.resignFirstResponder() + + let sheetContainer = AssetPickerSheetContainerViewController() + sheetContainer.assetPicker.assetPickerDelegate = self + present(sheetContainer, animated: true) } @objc func postButtonPressed() { @@ -478,7 +468,7 @@ class ComposeViewController: UIViewController { let group = DispatchGroup() var attachments: [Attachment?] = [] - for asset in selectedAssets { + for compAttachment in selectedAttachments { let index = attachments.count attachments.append(nil) @@ -487,36 +477,17 @@ class ComposeViewController: UIViewController { group.enter() - let options = PHImageRequestOptions() - options.version = .current - options.deliveryMode = .highQualityFormat - options.resizeMode = .none - options.isNetworkAccessAllowed = true - PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in - guard var data = data, let dataUTI = dataUTI else { fatalError() } - - let mimeType: String - if dataUTI == "public.heic" { - // neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG - let image = CIImage(data: data)! - let context = CIContext() - let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! - data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])! - mimeType = "image/jpeg" - } else { - mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String - } - + compAttachment.getData { (data, mimeType) in self.postProgressView.step() let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description) MastodonController.client.run(request) { (response) in guard case let .success(attachment, _) = response else { fatalError() } - + attachments[index] = attachment - + self.postProgressView.step() - + group.leave() } } @@ -588,30 +559,29 @@ extension ComposeViewController: UITextViewDelegate { } } -extension ComposeViewController: GMImagePickerControllerDelegate { - func assetsPickerController(_ picker: GMImagePickerController!, didFinishPickingAssets assets: [Any]!) { - let assets = assets as! [PHAsset] - selectedAssets.append(contentsOf: assets) - picker.dismiss(animated: true) - } - - func assetsPickerController(_ picker: GMImagePickerController!, shouldSelect asset: PHAsset!) -> Bool { +extension ComposeViewController: AssetPickerViewControllerDelegate { + func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool { switch MastodonController.instance.instanceType { case .pleroma: return true case .mastodon: - if (asset.mediaType == .video && selectedAssets.count > 0) || selectedAssets.first(where: { $0.mediaType == .video }) != nil { + if (type == .video && selectedAttachments.count > 0) || + selectedAttachments.contains(where: { $0.type == .video }) || + assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) { return false } - return selectedAssets.count + picker.selectedAssets.count < 4 + return selectedAttachments.count + assetPicker.currentCollectionSelectedAssets.count < 4 } } + func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) { + selectedAttachments.append(contentsOf: attachments) + } } extension ComposeViewController: ComposeMediaViewDelegate { func didRemoveMedia(_ mediaView: ComposeMediaView) { let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)! - selectedAssets.remove(at: index) + selectedAttachments.remove(at: index) updateAddAttachmentButton() } } @@ -650,22 +620,12 @@ extension ComposeViewController: DraftsTableViewControllerDelegate { updatePlaceholder() updateCharactersRemaining() - - let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil) - var assets = [String: (asset: PHAsset, description: String)]() - var addedAssets = 0 - while addedAssets < result.count { - let asset = result[addedAssets] - let attachment = draft.attachments.first(where: { $0.assetIdentifier == asset.localIdentifier })! - assets[asset.localIdentifier] = (asset, attachment.description) - addedAssets += 1 - } - - self.selectedAssets = assets.values.map { $0.asset } + + selectedAttachments = draft.attachments.map { $0.attachment } updateAttachmentViews() for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews { - let attachment = draft.attachments.first(where: { $0.assetIdentifier == mediaView.assetIdentifier })! + let attachment = draft.attachments.first(where: { $0.attachment == mediaView.attachment })! mediaView.descriptionTextView.text = attachment.description // call the delegate method manually, since setting the text property doesn't call it @@ -675,10 +635,10 @@ extension ComposeViewController: DraftsTableViewControllerDelegate { func draftSelectionCompleted() { // check that all the assets from the draft have been added - if let currentDraft = currentDraft, selectedAssets.count < currentDraft.attachments.count { + if let currentDraft = currentDraft, selectedAttachments.count < currentDraft.attachments.count { // some of the assets in the draft weren't loaded, so notify the user - let difference = currentDraft.attachments.count - selectedAssets.count + let difference = currentDraft.attachments.count - selectedAttachments.count // todo: localize me let suffix = difference == 1 ? "" : "s" let verb = difference == 1 ? "was" : "were" diff --git a/Tusker/Screens/Compose/CompositionAttachment.swift b/Tusker/Screens/Compose/CompositionAttachment.swift new file mode 100644 index 00000000..87a8fe35 --- /dev/null +++ b/Tusker/Screens/Compose/CompositionAttachment.swift @@ -0,0 +1,183 @@ +// +// CompositionAttachment.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos +import MobileCoreServices + +enum CompositionAttachment { + case asset(PHAsset) + case image(UIImage) + case video(URL) + + var type: AttachmentType { + switch self { + case let .asset(asset): + return asset.attachmentType! + case .image(_): + return .image + case .video(_): + return .video + } + } + + var isAsset: Bool { + switch self { + case .asset(_): + return true + default: + return false + } + } + + var canSaveToDraft: Bool { + switch self { + case .video(_): + return false + default: + return true + } + } + + func getData(completion: @escaping (Data, String) -> Void) { + switch self { + case let .image(image): + completion(image.pngData()!, "image/png") + case let .asset(asset): + if asset.mediaType == .image { + let options = PHImageRequestOptions() + options.version = .current + options.deliveryMode = .highQualityFormat + options.resizeMode = .none + options.isNetworkAccessAllowed = true + PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in + guard var data = data, let dataUTI = dataUTI else { fatalError() } + + let mimeType: String + if dataUTI == "public.heic" { + // neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG + let image = CIImage(data: data)! + let context = CIContext() + let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)! + data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])! + mimeType = "image/jpeg" + } else { + mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String + } + + completion(data, mimeType) + } + } else if asset.mediaType == .video { + let options = PHVideoRequestOptions() + options.deliveryMode = .automatic + options.isNetworkAccessAllowed = true + options.version = .current + PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { (exportSession, info) in + guard let exportSession = exportSession else { fatalError("failed to create export session") } + CompositionAttachment.exportVideoData(session: exportSession, completion: completion) + } + } else { + fatalError("assetType must be either image or video") + } + case let .video(url): + let asset = AVURLAsset(url: url) + guard let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else { + fatalError("failed to create export session") + } + CompositionAttachment.exportVideoData(session: session, completion: completion) + } + } + + private static func exportVideoData(session: AVAssetExportSession, completion: @escaping (Data, String) -> Void) { + session.outputFileType = .mp4 + session.outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("exported_video_\(UUID())").appendingPathExtension("mp4") + session.exportAsynchronously { + guard session.status == .completed else { fatalError("video export failed: \(String(describing: session.error))") } + do { + let data = try Data(contentsOf: session.outputURL!) + completion(data, "video/mp4") + } catch { + fatalError("Unable to load video: \(error)") + } + } + } + + enum AttachmentType { + case image, video + } +} + +extension PHAsset { + var attachmentType: CompositionAttachment.AttachmentType? { + switch self.mediaType { + case .image: + return .image + case .video: + return .video + default: + return nil + } + } +} + +extension CompositionAttachment: Codable { + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .asset(asset): + try container.encode("asset", forKey: .type) + try container.encode(asset.localIdentifier, forKey: .assetIdentifier) + case let .image(image): + try container.encode("image", forKey: .type) + try container.encode(image.pngData()!, forKey: .imageData) + case .video(_): + throw EncodingError.invalidValue(self, EncodingError.Context(codingPath: [], debugDescription: "video CompositionAttachments cannot be encoded")) + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + switch try container.decode(String.self, forKey: .type) { + case "asset": + let identifier = try container.decode(String.self, forKey: .assetIdentifier) + guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject else { + throw DecodingError.dataCorruptedError(forKey: .assetIdentifier, in: container, debugDescription: "Could not fetch asset with local identifier") + } + self = .asset(asset) + case "image": + guard let image = UIImage(data: try container.decode(Data.self, forKey: .imageData)) else { + throw DecodingError.dataCorruptedError(forKey: .imageData, in: container, debugDescription: "Could not decode UIImage from image data") + } + self = .image(image) + default: + throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "CompositionAttachment type must be one of 'image' or 'asset'") + } + } + + enum CodingKeys: CodingKey { + case type + case imageData + /// The local identifier of the PHAsset for this attachment + case assetIdentifier + } +} + +extension CompositionAttachment: Equatable { + static func ==(lhs: CompositionAttachment, rhs: CompositionAttachment) -> Bool { + switch (lhs, rhs) { + case let (.asset(a), .asset(b)): + return a.localIdentifier == b.localIdentifier + case let (.image(a), .image(b)): + return a == b + default: + return false + } + } +} diff --git a/Tusker/Screens/Compose/Drafts/DraftTableViewCell.swift b/Tusker/Screens/Compose/Drafts/DraftTableViewCell.swift index 5b8dfb22..b8414f0f 100644 --- a/Tusker/Screens/Compose/Drafts/DraftTableViewCell.swift +++ b/Tusker/Screens/Compose/Drafts/DraftTableViewCell.swift @@ -25,12 +25,8 @@ class DraftTableViewCell: UITableViewCell { attachmentsStackViewContainer.isHidden = draft.attachments.count == 0 - let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil) - var i = 0 - while i < result.count { - let asset = result[i] + for attachment in draft.attachments { let size = CGSize(width: 50, height: 50) - let imageView = UIImageView(frame: CGRect(origin: .zero, size: size)) imageView.contentMode = .scaleAspectFill imageView.layer.masksToBounds = true @@ -38,10 +34,17 @@ class DraftTableViewCell: UITableViewCell { attachmentsStackView.addArrangedSubview(imageView) imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true - PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in + switch attachment.attachment { + case let .asset(asset): + PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in + imageView.image = image + } + case let .image(image): imageView.image = image + case .video(_): + // videos aren't saved to drafts, so this is unreachable + return } - i += 1 } } diff --git a/Tusker/Views/Asset Picker/AlbumTableViewCell.swift b/Tusker/Views/Asset Picker/AlbumTableViewCell.swift new file mode 100644 index 00000000..03747aa2 --- /dev/null +++ b/Tusker/Views/Asset Picker/AlbumTableViewCell.swift @@ -0,0 +1,42 @@ +// +// AlbumTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/4/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +class AlbumTableViewCell: UITableViewCell { + + @IBOutlet weak var thumbnailImageView: UIImageView! + @IBOutlet weak var albumTitleLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + + thumbnailImageView.layer.masksToBounds = true + thumbnailImageView.layer.cornerRadius = 0.05 * thumbnailImageView.bounds.width + } + + func updateUI(album: PHAssetCollection) { + albumTitleLabel.text = album.localizedTitle + + let options = PHFetchOptions() + options.fetchLimit = 1 + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + let fetchResult = PHAsset.fetchAssets(in: album, options: options) + if fetchResult.count == 1 { + let asset = fetchResult.object(at: 0) + let size = thumbnailImageView.bounds.size + PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in + self.thumbnailImageView.image = image + } + } else { + thumbnailImageView.image = nil + } + } + +} diff --git a/Tusker/Views/Asset Picker/AlbumTableViewCell.xib b/Tusker/Views/Asset Picker/AlbumTableViewCell.xib new file mode 100644 index 00000000..a33cec0d --- /dev/null +++ b/Tusker/Views/Asset Picker/AlbumTableViewCell.xib @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/Asset Picker/AllPhotosTableViewCell.swift b/Tusker/Views/Asset Picker/AllPhotosTableViewCell.swift new file mode 100644 index 00000000..4a433986 --- /dev/null +++ b/Tusker/Views/Asset Picker/AllPhotosTableViewCell.swift @@ -0,0 +1,37 @@ +// +// AllPhotosTableViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/4/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +class AllPhotosTableViewCell: UITableViewCell { + + @IBOutlet weak var thumbnailImageView: UIImageView! + + override func awakeFromNib() { + super.awakeFromNib() + + thumbnailImageView.layer.masksToBounds = true + thumbnailImageView.layer.cornerRadius = 0.05 * thumbnailImageView.bounds.width + + let options = PHFetchOptions() + options.fetchLimit = 1 + options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + let fetchResult = PHAsset.fetchAssets(with: options) + if fetchResult.count == 1 { + let asset = fetchResult.object(at: 0) + let size = thumbnailImageView.bounds.size + PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in + self.thumbnailImageView.image = image + } + } else { + thumbnailImageView.image = nil + } + } + +} diff --git a/Tusker/Views/Asset Picker/AllPhotosTableViewCell.xib b/Tusker/Views/Asset Picker/AllPhotosTableViewCell.xib new file mode 100644 index 00000000..27a9b786 --- /dev/null +++ b/Tusker/Views/Asset Picker/AllPhotosTableViewCell.xib @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/Asset Picker/AssetCollectionViewCell.swift b/Tusker/Views/Asset Picker/AssetCollectionViewCell.swift new file mode 100644 index 00000000..58be6e5c --- /dev/null +++ b/Tusker/Views/Asset Picker/AssetCollectionViewCell.swift @@ -0,0 +1,74 @@ +// +// AssetCollectionViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/1/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import UIKit +import Photos + +class AssetCollectionViewCell: UICollectionViewCell { + + static let durationFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .positional + formatter.allowedUnits = [.minute, .second] + formatter.zeroFormattingBehavior = .pad + return formatter + }() + + var assetIdentifier: String! + var thumbnailImage: UIImage? { + get { + imageView.image + } + set { + imageView.image = newValue + } + } + + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var checkmarkVisualEffectView: UIVisualEffectView! + @IBOutlet weak var livePhotoVisualEffectView: UIVisualEffectView! + @IBOutlet weak var durationVisualEffectView: UIVisualEffectView! + @IBOutlet weak var durationLabel: UILabel! + + override var isSelected: Bool { + didSet { + checkmarkVisualEffectView.isHidden = !isSelected + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + checkmarkVisualEffectView.layer.masksToBounds = true + checkmarkVisualEffectView.layer.cornerRadius = 12 + checkmarkVisualEffectView.isHidden = true + + livePhotoVisualEffectView.layer.masksToBounds = true + livePhotoVisualEffectView.layer.cornerRadius = 12 + + durationVisualEffectView.layer.masksToBounds = true + durationVisualEffectView.layer.cornerRadius = durationVisualEffectView.bounds.width * 0.1 + } + + func updateUI(asset: PHAsset) { + assetIdentifier = asset.localIdentifier + + durationVisualEffectView.isHidden = asset.mediaType != .video + if asset.mediaType == .video { + durationLabel.text = AssetCollectionViewCell.durationFormatter.string(from: asset.duration) + } + + livePhotoVisualEffectView.isHidden = !(asset.mediaType == .image && asset.mediaSubtypes.contains(.photoLive)) + } + + override func prepareForReuse() { + thumbnailImage = nil + isSelected = false + } + +} diff --git a/Tusker/Views/Asset Picker/AssetCollectionViewCell.xib b/Tusker/Views/Asset Picker/AssetCollectionViewCell.xib new file mode 100644 index 00000000..f6f5d5e8 --- /dev/null +++ b/Tusker/Views/Asset Picker/AssetCollectionViewCell.xib @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib b/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib new file mode 100644 index 00000000..e237e162 --- /dev/null +++ b/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tusker/Views/Compose Media/ComposeMediaView.swift b/Tusker/Views/Compose Media/ComposeMediaView.swift index bd76335f..d11a418b 100644 --- a/Tusker/Views/Compose Media/ComposeMediaView.swift +++ b/Tusker/Views/Compose Media/ComposeMediaView.swift @@ -8,6 +8,7 @@ import UIKit import Photos +import AVFoundation protocol ComposeMediaViewDelegate { func didRemoveMedia(_ mediaView: ComposeMediaView) @@ -21,7 +22,7 @@ class ComposeMediaView: UIView { @IBOutlet weak var descriptionTextView: UITextView! @IBOutlet weak var placeholderLabel: UILabel! - var assetIdentifier: String? + var attachment: CompositionAttachment! static func create() -> ComposeMediaView { return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeMediaView @@ -36,12 +37,24 @@ class ComposeMediaView: UIView { descriptionTextView.delegate = self } - func update(asset: PHAsset) { - self.assetIdentifier = asset.localIdentifier + func update(attachment: CompositionAttachment) { + self.attachment = attachment - let size = CGSize(width: 80, height: 80) - PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in - self.imageView.image = image + switch attachment { + case let .image(image): + imageView.image = image + case let .asset(asset): + let size = CGSize(width: 80, height: 80) + PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in + guard self.attachment == attachment else { return } + self.imageView.image = image + } + case let .video(url): + let asset = AVURLAsset(url: url) + let imageGenerator = AVAssetImageGenerator(asset: asset) + if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) { + imageView.image = UIImage(cgImage: cgImage) + } } }