diff --git a/GMImagePicker/Base.lproj/GMImagePicker.strings b/GMImagePicker/Base.lproj/GMImagePicker.strings deleted file mode 100644 index cd03baa6..00000000 Binary files a/GMImagePicker/Base.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/GMAlbumsViewCell.h b/GMImagePicker/GMAlbumsViewCell.h deleted file mode 100644 index 31a2676d..00000000 --- a/GMImagePicker/GMAlbumsViewCell.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// GMAlbumsViewCell.h -// GMPhotoPicker -// -// Created by Guillermo Muntaner Perelló on 22/09/14. -// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved. -// - -#include -#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 d2789c72..00000000 Binary files a/GMImagePicker/GMEmptyFolder@1x.png and /dev/null differ diff --git a/GMImagePicker/GMEmptyFolder@2x.png b/GMImagePicker/GMEmptyFolder@2x.png deleted file mode 100644 index 58848ae1..00000000 Binary files a/GMImagePicker/GMEmptyFolder@2x.png and /dev/null differ diff --git a/GMImagePicker/GMGridViewCell.h b/GMImagePicker/GMGridViewCell.h deleted file mode 100644 index 365cfdb1..00000000 --- a/GMImagePicker/GMGridViewCell.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// GMGridViewCell.h -// GMPhotoPicker -// -// Created by Guillermo Muntaner Perelló on 19/09/14. -// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved. -// - -#include -#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 b251b6cd..00000000 Binary files a/GMImagePicker/GMSelected.png and /dev/null differ diff --git a/GMImagePicker/GMSelected@2x.png b/GMImagePicker/GMSelected@2x.png deleted file mode 100755 index 5a8f6f8c..00000000 Binary files a/GMImagePicker/GMSelected@2x.png and /dev/null differ diff --git a/GMImagePicker/GMVideoIcon.png b/GMImagePicker/GMVideoIcon.png deleted file mode 100644 index 865e68d2..00000000 Binary files a/GMImagePicker/GMVideoIcon.png and /dev/null differ diff --git a/GMImagePicker/GMVideoIcon@2x.png b/GMImagePicker/GMVideoIcon@2x.png deleted file mode 100644 index 4ed325c5..00000000 Binary files a/GMImagePicker/GMVideoIcon@2x.png and /dev/null differ diff --git a/GMImagePicker/Info.plist b/GMImagePicker/Info.plist deleted file mode 100644 index e1fe4cfb..00000000 --- a/GMImagePicker/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 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 b33a4deb..00000000 Binary files a/GMImagePicker/ca.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/de.lproj/GMImagePicker.strings b/GMImagePicker/de.lproj/GMImagePicker.strings deleted file mode 100644 index 0e384dce..00000000 Binary files a/GMImagePicker/de.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/en.lproj/GMImagePicker.strings b/GMImagePicker/en.lproj/GMImagePicker.strings deleted file mode 100644 index b1836b63..00000000 Binary files a/GMImagePicker/en.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/es.lproj/GMImagePicker.strings b/GMImagePicker/es.lproj/GMImagePicker.strings deleted file mode 100644 index 147c276c..00000000 Binary files a/GMImagePicker/es.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/fr.lproj/GMImagePicker.strings b/GMImagePicker/fr.lproj/GMImagePicker.strings deleted file mode 100644 index 0be5d582..00000000 Binary files a/GMImagePicker/fr.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/it.lproj/GMImagePicker.strings b/GMImagePicker/it.lproj/GMImagePicker.strings deleted file mode 100644 index f6946b32..00000000 Binary files a/GMImagePicker/it.lproj/GMImagePicker.strings and /dev/null differ diff --git a/GMImagePicker/pt.lproj/GMImagePicker.strings b/GMImagePicker/pt.lproj/GMImagePicker.strings deleted file mode 100644 index 2f3caedc..00000000 Binary files a/GMImagePicker/pt.lproj/GMImagePicker.strings and /dev/null differ diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index a71562d9..2a4fb9bd 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -25,9 +25,6 @@ 04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; }; 04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; }; D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; }; - D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; }; - D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; }; D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; }; D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; }; @@ -76,6 +73,13 @@ D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; + D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; }; + D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; }; + D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; }; + D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; }; + D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; }; + D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493B23C1000300612E6E /* AlbumTableViewCell.xib */; }; + D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */; }; D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; }; D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; }; D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; }; @@ -109,6 +113,7 @@ D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; }; D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; }; D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; }; + D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; }; D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; }; @@ -142,30 +147,6 @@ D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; }; D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; }; D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; }; - D686329521ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686326E21ED8312008C716E /* GMImagePicker.strings */; }; - D686329621ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327121ED8312008C716E /* GMImagePicker.strings */; }; - D686329721ED8319008C716E /* GMGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327321ED8312008C716E /* GMGridViewCell.m */; }; - D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327421ED8312008C716E /* GMVideoIcon@2x.png */; }; - D686329921ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327621ED8313008C716E /* GMImagePicker.strings */; }; - D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327821ED8313008C716E /* GMAlbumsViewController.m */; }; - D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327921ED8313008C716E /* GMVideoIcon.png */; }; - D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327B21ED8313008C716E /* GMImagePicker.strings */; }; - D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */; }; - D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327E21ED8314008C716E /* GMAlbumsViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D686329F21ED8319008C716E /* GMGridViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327F21ED8315008C716E /* GMGridViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D68632A021ED8319008C716E /* GMSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328021ED8315008C716E /* GMSelected.png */; }; - D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328121ED8315008C716E /* GMAlbumsViewCell.m */; }; - D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328221ED8316008C716E /* GMAlbumsViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328421ED8316008C716E /* GMImagePicker.strings */; }; - D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328721ED8317008C716E /* GMImagePicker.strings */; }; - D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328921ED8317008C716E /* GMSelected@2x.png */; }; - D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328A21ED8317008C716E /* GMImagePickerController.m */; }; - D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328B21ED8317008C716E /* GMGridViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328D21ED8317008C716E /* GMImagePicker.strings */; }; - D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */; }; - D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; }; - D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; }; D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; }; D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; }; D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; }; @@ -194,6 +175,13 @@ D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; }; D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; }; D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */; }; + D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0539E23BD2BA300A066FA /* SheetController */; }; + D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; }; + D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; }; + D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; }; + D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; }; + D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; }; + D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */; }; D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; }; D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; }; D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -229,13 +217,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; - proxyType = 1; - remoteGlobalIDString = D60A548A21ED515800F1F87C; - remoteInfo = GMImagePicker; - }; D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; @@ -280,7 +261,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */, D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */, D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */, D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */, @@ -311,8 +291,6 @@ D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = ""; }; D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = ""; }; D60A548B21ED515800F1F87C /* GMImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GMImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D60A548D21ED515800F1F87C /* GMImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GMImagePicker.h; sourceTree = ""; }; - D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = ""; }; D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = ""; }; D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -362,6 +340,13 @@ D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = ""; }; D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = ""; }; D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = ""; }; + D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = ""; }; + D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = ""; }; + D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = ""; }; + D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = ""; }; + D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = ""; }; + D626493B23C1000300612E6E /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = ""; }; + D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumAssetCollectionViewController.swift; sourceTree = ""; }; D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = ""; }; D627943423A5525100D38C68 /* StatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivity.swift; sourceTree = ""; }; D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = ""; }; @@ -394,6 +379,7 @@ D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = ""; }; D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = ""; }; D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = ""; }; + D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = ""; }; D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = ""; }; @@ -428,30 +414,6 @@ D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = ""; }; D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = ""; }; D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = ""; }; - D686326F21ED8312008C716E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = GMImagePicker.strings; sourceTree = ""; }; - D686327221ED8312008C716E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = GMImagePicker.strings; sourceTree = ""; }; - D686327321ED8312008C716E /* GMGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewCell.m; sourceTree = ""; }; - D686327421ED8312008C716E /* GMVideoIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMVideoIcon@2x.png"; sourceTree = ""; }; - D686327721ED8313008C716E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = GMImagePicker.strings; sourceTree = ""; }; - D686327821ED8313008C716E /* GMAlbumsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewController.m; sourceTree = ""; }; - D686327921ED8313008C716E /* GMVideoIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMVideoIcon.png; sourceTree = ""; }; - D686327C21ED8313008C716E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = GMImagePicker.strings; sourceTree = ""; }; - D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@2x.png"; sourceTree = ""; }; - D686327E21ED8314008C716E /* GMAlbumsViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewCell.h; sourceTree = ""; }; - D686327F21ED8315008C716E /* GMGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewController.h; sourceTree = ""; }; - D686328021ED8315008C716E /* GMSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMSelected.png; sourceTree = ""; }; - D686328121ED8315008C716E /* GMAlbumsViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewCell.m; sourceTree = ""; }; - D686328221ED8316008C716E /* GMAlbumsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewController.h; sourceTree = ""; }; - D686328521ED8316008C716E /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = GMImagePicker.strings; sourceTree = ""; }; - D686328821ED8317008C716E /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = GMImagePicker.strings; sourceTree = ""; }; - D686328921ED8317008C716E /* GMSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMSelected@2x.png"; sourceTree = ""; }; - D686328A21ED8317008C716E /* GMImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMImagePickerController.m; sourceTree = ""; }; - D686328B21ED8317008C716E /* GMGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewCell.h; sourceTree = ""; }; - D686328E21ED8317008C716E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = GMImagePicker.strings; sourceTree = ""; }; - D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@1x.png"; sourceTree = ""; }; - D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewController.m; sourceTree = ""; }; - D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = ""; }; - D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = ""; }; D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = ""; }; D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = ""; }; D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = ""; }; @@ -480,6 +442,12 @@ D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowAccountActivity.swift; sourceTree = ""; }; D6AEBB4723216B1D00E5038B /* AccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivity.swift; sourceTree = ""; }; D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowAccountActivity.swift; sourceTree = ""; }; + D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerViewController.swift; sourceTree = ""; }; + D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionsListViewController.swift; sourceTree = ""; }; + D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = ""; }; + D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = ""; }; + D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = ""; }; + D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerSheetContainerViewController.swift; sourceTree = ""; }; D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = ""; }; D6BC874421961F73006163F1 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = ""; }; @@ -549,8 +517,8 @@ files = ( D65F613823AFD65D00F3CFD3 /* Embassy.framework in Frameworks */, D65F613623AFD65900F3CFD3 /* Ambassador.framework in Frameworks */, - D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */, D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */, + D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */, D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */, D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */, 0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */, @@ -601,39 +569,6 @@ path = Transitions; sourceTree = ""; }; - D60A548C21ED515800F1F87C /* GMImagePicker */ = { - isa = PBXGroup; - children = ( - D60A548D21ED515800F1F87C /* GMImagePicker.h */, - D60A548E21ED515800F1F87C /* Info.plist */, - D686327A21ED8313008C716E /* Base.lproj */, - D686327021ED8312008C716E /* ca.lproj */, - D686329221ED8319008C716E /* de.lproj */, - D686328C21ED8317008C716E /* en.lproj */, - D686326D21ED8312008C716E /* es.lproj */, - D686328321ED8316008C716E /* fr.lproj */, - D686327E21ED8314008C716E /* GMAlbumsViewCell.h */, - D686328121ED8315008C716E /* GMAlbumsViewCell.m */, - D686328221ED8316008C716E /* GMAlbumsViewController.h */, - D686327821ED8313008C716E /* GMAlbumsViewController.m */, - D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */, - D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */, - D686328B21ED8317008C716E /* GMGridViewCell.h */, - D686327321ED8312008C716E /* GMGridViewCell.m */, - D686327F21ED8315008C716E /* GMGridViewController.h */, - D686329021ED8319008C716E /* GMGridViewController.m */, - D686329121ED8319008C716E /* GMImagePickerController.h */, - D686328A21ED8317008C716E /* GMImagePickerController.m */, - D686328021ED8315008C716E /* GMSelected.png */, - D686328921ED8317008C716E /* GMSelected@2x.png */, - D686327921ED8313008C716E /* GMVideoIcon.png */, - D686327421ED8312008C716E /* GMVideoIcon@2x.png */, - D686327521ED8313008C716E /* it.lproj */, - D686328621ED8317008C716E /* pt.lproj */, - ); - path = GMImagePicker; - sourceTree = ""; - }; D60C07E221E817560057FAA8 /* Compose Media */ = { isa = PBXGroup; children = ( @@ -748,6 +683,20 @@ path = "Instance Cell"; sourceTree = ""; }; + D626494023C122C800612E6E /* Asset Picker */ = { + isa = PBXGroup; + children = ( + D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */, + D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */, + D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */, + D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */, + D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */, + D626493A23C1000300612E6E /* AlbumTableViewCell.swift */, + D626493B23C1000300612E6E /* AlbumTableViewCell.xib */, + ); + path = "Asset Picker"; + sourceTree = ""; + }; D627943323A5523800D38C68 /* Status Activities */ = { isa = PBXGroup; children = ( @@ -889,9 +838,11 @@ D641C787213DD862004B4513 /* Compose */ = { isa = PBXGroup; children = ( + D6B053A023BD2BED00A066FA /* Asset Picker */, D627FF77217E94F200CC0648 /* Drafts */, D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */, D66362702136338600C9CBA2 /* ComposeViewController.swift */, + D626493423BD94CE00612E6E /* CompositionAttachment.swift */, D6285B5221EA708700FE4B39 /* StatusFormat.swift */, ); path = Compose; @@ -1033,70 +984,6 @@ path = "Compose Status Reply"; sourceTree = ""; }; - D686326D21ED8312008C716E /* es.lproj */ = { - isa = PBXGroup; - children = ( - D686326E21ED8312008C716E /* GMImagePicker.strings */, - ); - path = es.lproj; - sourceTree = ""; - }; - D686327021ED8312008C716E /* ca.lproj */ = { - isa = PBXGroup; - children = ( - D686327121ED8312008C716E /* GMImagePicker.strings */, - ); - path = ca.lproj; - sourceTree = ""; - }; - D686327521ED8313008C716E /* it.lproj */ = { - isa = PBXGroup; - children = ( - D686327621ED8313008C716E /* GMImagePicker.strings */, - ); - path = it.lproj; - sourceTree = ""; - }; - D686327A21ED8313008C716E /* Base.lproj */ = { - isa = PBXGroup; - children = ( - D686327B21ED8313008C716E /* GMImagePicker.strings */, - ); - path = Base.lproj; - sourceTree = ""; - }; - D686328321ED8316008C716E /* fr.lproj */ = { - isa = PBXGroup; - children = ( - D686328421ED8316008C716E /* GMImagePicker.strings */, - ); - path = fr.lproj; - sourceTree = ""; - }; - D686328621ED8317008C716E /* pt.lproj */ = { - isa = PBXGroup; - children = ( - D686328721ED8317008C716E /* GMImagePicker.strings */, - ); - path = pt.lproj; - sourceTree = ""; - }; - D686328C21ED8317008C716E /* en.lproj */ = { - isa = PBXGroup; - children = ( - D686328D21ED8317008C716E /* GMImagePicker.strings */, - ); - path = en.lproj; - sourceTree = ""; - }; - D686329221ED8319008C716E /* de.lproj */ = { - isa = PBXGroup; - children = ( - D686329321ED8319008C716E /* GMImagePicker.strings */, - ); - path = de.lproj; - sourceTree = ""; - }; D6A3BC7223218C6E00FD64D5 /* Utilities */ = { isa = PBXGroup; children = ( @@ -1174,6 +1061,19 @@ path = "Account Activities"; sourceTree = ""; }; + D6B053A023BD2BED00A066FA /* Asset Picker */ = { + isa = PBXGroup; + children = ( + D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */, + D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */, + D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */, + D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */, + D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */, + D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */, + ); + path = "Asset Picker"; + sourceTree = ""; + }; D6BC9DD8232D8BCA002CA326 /* Search */ = { isa = PBXGroup; children = ( @@ -1196,6 +1096,7 @@ D67C57A721E2649B00C3118B /* Account Detail */, D67C57B021E28F9400C3118B /* Compose Status Reply */, D60C07E221E817560057FAA8 /* Compose Media */, + D626494023C122C800612E6E /* Asset Picker */, D641C78A213DD926004B4513 /* Status */, D6C7D27B22B6EBE200071952 /* Attachments */, D641C78B213DD92F004B4513 /* Profile Header */, @@ -1238,7 +1139,6 @@ D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */, D61099AC2144B0CC00432DC2 /* Pachyderm */, D61099B92144B0CC00432DC2 /* PachydermTests */, - D60A548C21ED515800F1F87C /* GMImagePicker */, D6D4DDCE212518A000E1C4BB /* Tusker */, D6D4DDE3212518A200E1C4BB /* TuskerTests */, D6D4DDEE212518A200E1C4BB /* TuskerUITests */, @@ -1333,12 +1233,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - D686329F21ED8319008C716E /* GMGridViewController.h in Headers */, - D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */, - D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */, - D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */, - D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */, - D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1422,9 +1316,11 @@ ); dependencies = ( D61099BF2144B0CC00432DC2 /* PBXTargetDependency */, - D60A549121ED515800F1F87C /* PBXTargetDependency */, ); name = Tusker; + packageProductDependencies = ( + D6B0539E23BD2BA300A066FA /* SheetController */, + ); productName = Tusker; productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */; productType = "com.apple.product-type.application"; @@ -1521,6 +1417,7 @@ ); mainGroup = D6D4DDC3212518A000E1C4BB; packageReferences = ( + D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */, ); productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */; projectDirPath = ""; @@ -1541,20 +1438,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */, - D686329621ED8319008C716E /* GMImagePicker.strings in Resources */, - D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */, - D686329521ED8319008C716E /* GMImagePicker.strings in Resources */, - D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */, - D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */, - D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */, - D686329921ED8319008C716E /* GMImagePicker.strings in Resources */, - D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */, - D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */, - D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */, - D68632A021ED8319008C716E /* GMSelected.png in Resources */, - D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */, - D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1578,16 +1461,20 @@ files = ( D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */, D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */, + D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */, D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */, D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */, D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */, D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */, D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */, + D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */, D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */, D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */, D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */, D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */, D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */, + D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */, + D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */, D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */, D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */, 0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */, @@ -1644,11 +1531,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */, - D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */, - D686329721ED8319008C716E /* GMGridViewCell.m in Sources */, - D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */, - D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1713,9 +1595,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */, D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */, D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */, 0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */, + D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */, D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */, @@ -1754,6 +1638,7 @@ 0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */, D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */, D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */, + D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */, 04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */, D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */, D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */, @@ -1768,6 +1653,7 @@ D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */, D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */, D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */, + D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */, D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */, D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */, D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */, @@ -1779,11 +1665,15 @@ D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */, D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */, + D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */, D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */, D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */, D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */, + D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */, D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */, + D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */, D663626221360B1900C9CBA2 /* Preferences.swift in Sources */, + D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */, D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */, D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */, D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */, @@ -1817,12 +1707,14 @@ 0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */, D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */, D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */, + D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */, D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */, D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */, D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */, D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */, D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, + D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */, D6163F2C21AA0AF1008DAC41 /* MyProfileTableViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1851,11 +1743,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - D60A549121ED515800F1F87C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D60A548A21ED515800F1F87C /* GMImagePicker */; - targetProxy = D60A549021ED515800F1F87C /* PBXContainerItemProxy */; - }; D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D61099AA2144B0CC00432DC2 /* Pachyderm */; @@ -1884,70 +1771,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - D686326E21ED8312008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686326F21ED8312008C716E /* es */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686327121ED8312008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686327221ED8312008C716E /* ca */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686327621ED8313008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686327721ED8313008C716E /* it */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686327B21ED8313008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686327C21ED8313008C716E /* Base */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686328421ED8316008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686328521ED8316008C716E /* fr */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686328721ED8317008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686328821ED8317008C716E /* pt */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686328D21ED8317008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686328E21ED8317008C716E /* en */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; - D686329321ED8319008C716E /* GMImagePicker.strings */ = { - isa = PBXVariantGroup; - children = ( - D686329421ED8319008C716E /* de */, - ); - name = GMImagePicker.strings; - sourceTree = ""; - }; D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -2426,6 +2249,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://git.shadowfacts.net/shadowfacts/SheetController.git"; + requirement = { + branch = master; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D6B0539E23BD2BA300A066FA /* SheetController */ = { + isa = XCSwiftPackageProductDependency; + package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */; + productName = SheetController; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D6D4DDC4212518A000E1C4BB /* Project object */; } diff --git a/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..dbc2ec1a --- /dev/null +++ b/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SheetController", + "repositoryURL": "https://git.shadowfacts.net/shadowfacts/SheetController.git", + "state": { + "branch": "master", + "revision": "6ee1ad24ec8620f5c17416d6141643f0787708ba", + "version": null + } + } + ] + }, + "version": 1 +} diff --git a/Tusker/DraftsManager.swift b/Tusker/DraftsManager.swift index 6cfccee3..23e9a5eb 100644 --- a/Tusker/DraftsManager.swift +++ b/Tusker/DraftsManager.swift @@ -83,8 +83,7 @@ extension DraftsManager { } struct DraftAttachment: Codable { - /// The local identifier of the PHAsset for this attachment - let assetIdentifier: String + let attachment: CompositionAttachment let description: String } } diff --git a/Tusker/Info.plist b/Tusker/Info.plist index 5c319b92..937a979e 100644 --- a/Tusker/Info.plist +++ b/Tusker/Info.plist @@ -52,8 +52,10 @@ 1 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) + } } }