Replace GMImagePicker with custom asset picker based on SheetController
Fixes #23 Closes #50
This commit is contained in:
parent
b2956b6118
commit
3ecbb1895c
Binary file not shown.
|
@ -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 <UIKit/UIKit.h>
|
|
||||||
#include <Photos/Photos.h>
|
|
||||||
|
|
||||||
@interface GMAlbumsViewCell : UITableViewCell
|
|
||||||
|
|
||||||
@property (strong) PHFetchResult *assetsFetchResults;
|
|
||||||
@property (strong) PHAssetCollection *assetCollection;
|
|
||||||
|
|
||||||
//The labels
|
|
||||||
@property (nonatomic, strong) UILabel *titleLabel;
|
|
||||||
@property (nonatomic, strong) UILabel *infoLabel;
|
|
||||||
//The imageView
|
|
||||||
@property (nonatomic, strong) UIImageView *imageView1;
|
|
||||||
@property (nonatomic, strong) UIImageView *imageView2;
|
|
||||||
@property (nonatomic, strong) UIImageView *imageView3;
|
|
||||||
//Video additional information
|
|
||||||
@property (nonatomic, strong) UIImageView *videoIcon;
|
|
||||||
@property (nonatomic, strong) UIImageView *slowMoIcon;
|
|
||||||
@property (nonatomic, strong) UIView *gradientView;
|
|
||||||
@property (nonatomic, strong) CAGradientLayer *gradient;
|
|
||||||
//Selection overlay
|
|
||||||
|
|
||||||
- (void)setVideoLayout:(BOOL)isVideo;
|
|
||||||
@end
|
|
|
@ -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 <QuartzCore/QuartzCore.h>
|
|
||||||
|
|
||||||
@implementation GMAlbumsViewCell
|
|
||||||
|
|
||||||
- (void)awakeFromNib
|
|
||||||
{
|
|
||||||
[super awakeFromNib];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
|
||||||
{
|
|
||||||
|
|
||||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
|
|
||||||
{
|
|
||||||
// self.isAccessibilityElement = YES;
|
|
||||||
self.contentView.backgroundColor = [UIColor clearColor];
|
|
||||||
self.backgroundColor = [UIColor clearColor];
|
|
||||||
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
|
||||||
|
|
||||||
// Border width of 1 pixel:
|
|
||||||
float borderWidth = 1.0/[UIScreen mainScreen].scale;
|
|
||||||
|
|
||||||
// ImageView
|
|
||||||
_imageView3 = [UIImageView new];
|
|
||||||
_imageView3.contentMode = UIViewContentModeScaleAspectFill;
|
|
||||||
_imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
|
|
||||||
[_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
|
||||||
[_imageView3.layer setBorderWidth: borderWidth];
|
|
||||||
_imageView3.clipsToBounds = YES;
|
|
||||||
_imageView3.translatesAutoresizingMaskIntoConstraints = YES;
|
|
||||||
_imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
|
||||||
[self.contentView addSubview:_imageView3];
|
|
||||||
|
|
||||||
// ImageView
|
|
||||||
_imageView2 = [UIImageView new];
|
|
||||||
_imageView2.contentMode = UIViewContentModeScaleAspectFill;
|
|
||||||
_imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
|
|
||||||
[_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
|
||||||
[_imageView2.layer setBorderWidth: borderWidth];
|
|
||||||
_imageView2.clipsToBounds = YES;
|
|
||||||
_imageView2.translatesAutoresizingMaskIntoConstraints = YES;
|
|
||||||
_imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
|
||||||
[self.contentView addSubview:_imageView2];
|
|
||||||
|
|
||||||
// ImageView
|
|
||||||
_imageView1 = [UIImageView new];
|
|
||||||
_imageView1.contentMode = UIViewContentModeScaleAspectFill;
|
|
||||||
_imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
|
|
||||||
[_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
|
||||||
[_imageView1.layer setBorderWidth: borderWidth];
|
|
||||||
_imageView1.clipsToBounds = YES;
|
|
||||||
_imageView1.translatesAutoresizingMaskIntoConstraints = YES;
|
|
||||||
_imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
|
||||||
[self.contentView addSubview:_imageView1];
|
|
||||||
|
|
||||||
|
|
||||||
// The video gradient, label & icon
|
|
||||||
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
|
|
||||||
UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
|
|
||||||
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
|
|
||||||
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
|
|
||||||
_gradient = [CAGradientLayer layer];
|
|
||||||
_gradient.frame = _gradientView.bounds;
|
|
||||||
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
|
|
||||||
_gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
|
|
||||||
[_gradientView.layer insertSublayer:_gradient atIndex:0];
|
|
||||||
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
|
||||||
_gradientView.translatesAutoresizingMaskIntoConstraints = YES;
|
|
||||||
[self.imageView1 addSubview:_gradientView];
|
|
||||||
_gradientView.hidden = YES;
|
|
||||||
|
|
||||||
// VideoIcon
|
|
||||||
_videoIcon = [UIImageView new];
|
|
||||||
_videoIcon.contentMode = UIViewContentModeScaleAspectFill;
|
|
||||||
_videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
|
|
||||||
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMAlbumsViewCell.class] compatibleWithTraitCollection:nil];
|
|
||||||
_videoIcon.clipsToBounds = YES;
|
|
||||||
_videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
|
|
||||||
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
|
||||||
[self.imageView1 addSubview:_videoIcon];
|
|
||||||
_videoIcon.hidden = NO;
|
|
||||||
|
|
||||||
// TextLabel
|
|
||||||
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
|
|
||||||
self.textLabel.numberOfLines = 1;
|
|
||||||
|
|
||||||
self.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
|
|
||||||
self.detailTextLabel.numberOfLines = 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)layoutSubviews {
|
|
||||||
[super layoutSubviews];
|
|
||||||
self.textLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.textLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8, self.textLabel.frame.size.height);
|
|
||||||
self.detailTextLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.detailTextLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8 - kAlbumImageToTextSpace, self.detailTextLabel.frame.size.height);
|
|
||||||
|
|
||||||
}
|
|
||||||
- (void)setVideoLayout:(BOOL)isVideo
|
|
||||||
{
|
|
||||||
// TODO : Add additional icons for slowmo, burst, etc...
|
|
||||||
if (isVideo) {
|
|
||||||
_videoIcon.hidden = NO;
|
|
||||||
_gradientView.hidden = NO;
|
|
||||||
} else {
|
|
||||||
_videoIcon.hidden = YES;
|
|
||||||
_gradientView.hidden = YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super setSelected:selected animated:animated];
|
|
||||||
|
|
||||||
// Configure the view for the selected state
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
|
@ -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 <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
// Measuring IOS8 Photos APP at @2x (iPhone5s):
|
|
||||||
// The rows are 180px/90pts
|
|
||||||
// Left image border is 21px/10.5pts
|
|
||||||
// Separation between image and text is 42px/21pts (double the previouse one)
|
|
||||||
// The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
|
|
||||||
// The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
|
||||||
// The third image measures 123px/61.5pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
|
||||||
|
|
||||||
static int kAlbumRowHeight = 90;
|
|
||||||
static int kAlbumLeftToImageSpace = 10;
|
|
||||||
static int kAlbumImageToTextSpace = 21;
|
|
||||||
static float const kAlbumGradientHeight = 20.0f;
|
|
||||||
static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
|
|
||||||
static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
|
|
||||||
static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
|
|
||||||
|
|
||||||
|
|
||||||
@interface GMAlbumsViewController : UITableViewController
|
|
||||||
|
|
||||||
- (void)selectAllAlbumsCell;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -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 <Photos/Photos.h>
|
|
||||||
|
|
||||||
@interface GMAlbumsViewController() <PHPhotoLibraryChangeObserver>
|
|
||||||
|
|
||||||
@property (strong,nonatomic) NSArray *collectionsFetchResults;
|
|
||||||
@property (strong,nonatomic) NSArray *collectionsLocalizedTitles;
|
|
||||||
@property (strong,nonatomic) NSArray *collectionsFetchResultsAssets;
|
|
||||||
@property (strong,nonatomic) NSArray *collectionsFetchResultsTitles;
|
|
||||||
@property (nonatomic, weak) GMImagePickerController *picker;
|
|
||||||
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@implementation GMAlbumsViewController
|
|
||||||
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
if (self = [super initWithStyle:UITableViewStylePlain]) {
|
|
||||||
self.preferredContentSize = kPopoverContentSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
static NSString *const AllPhotosReuseIdentifier = @"AllPhotosCell";
|
|
||||||
static NSString *const CollectionCellReuseIdentifier = @"CollectionCell";
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
|
||||||
|
|
||||||
// Navigation bar customization
|
|
||||||
if (self.picker.customNavigationBarPrompt) {
|
|
||||||
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imageManager = [[PHCachingImageManager alloc] init];
|
|
||||||
|
|
||||||
// Table view aspect
|
|
||||||
self.tableView.rowHeight = kAlbumRowHeight;
|
|
||||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
NSDictionary *barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
|
||||||
|
|
||||||
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
|
||||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
|
||||||
style:UIBarButtonItemStylePlain
|
|
||||||
target:self.picker
|
|
||||||
action:@selector(dismiss:)];
|
|
||||||
if (self.picker.useCustomFontForNavigationBar) {
|
|
||||||
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
|
||||||
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.picker.allowsMultipleSelection) {
|
|
||||||
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
|
||||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
|
||||||
style:UIBarButtonItemStyleDone
|
|
||||||
target:self.picker
|
|
||||||
action:@selector(finishPickingAssets:)];
|
|
||||||
if (self.picker.useCustomFontForNavigationBar) {
|
|
||||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
|
||||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom toolbar
|
|
||||||
self.toolbarItems = self.picker.toolbarItems;
|
|
||||||
|
|
||||||
// Title
|
|
||||||
if (!self.picker.title) {
|
|
||||||
self.title = NSLocalizedStringFromTableInBundle(@"picker.navigation.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Navigation bar default title");
|
|
||||||
} else {
|
|
||||||
self.title = self.picker.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch PHAssetCollections:
|
|
||||||
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
|
|
||||||
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
|
|
||||||
self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
|
|
||||||
self.collectionsLocalizedTitles = @[NSLocalizedStringFromTableInBundle(@"picker.table.smart-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Smart Albums"),NSLocalizedStringFromTableInBundle(@"picker.table.user-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Albums")];
|
|
||||||
|
|
||||||
[self updateFetchResults];
|
|
||||||
|
|
||||||
// Register for changes
|
|
||||||
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
|
||||||
|
|
||||||
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
|
||||||
{
|
|
||||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
|
||||||
return self.picker.pickerStatusBarStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)selectAllAlbumsCell {
|
|
||||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
|
|
||||||
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)updateFetchResults
|
|
||||||
{
|
|
||||||
//What I do here is fetch both the albums list and the assets of each album.
|
|
||||||
//This way I have acces to the number of items in each album, I can load the 3
|
|
||||||
//thumbnails directly and I can pass the fetched result to the gridViewController.
|
|
||||||
|
|
||||||
self.collectionsFetchResultsAssets=nil;
|
|
||||||
self.collectionsFetchResultsTitles=nil;
|
|
||||||
|
|
||||||
//Fetch PHAssetCollections:
|
|
||||||
PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
|
|
||||||
PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
|
|
||||||
|
|
||||||
//All album: Sorted by descending creation date.
|
|
||||||
NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
|
|
||||||
NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
|
|
||||||
{
|
|
||||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
|
||||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
|
||||||
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
|
||||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
|
|
||||||
[allFetchResultArray addObject:assetsFetchResult];
|
|
||||||
[allFetchResultLabel addObject:NSLocalizedStringFromTableInBundle(@"picker.table.all-photos-label", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"All photos")];
|
|
||||||
}
|
|
||||||
|
|
||||||
//User albums:
|
|
||||||
NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
|
|
||||||
NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
|
|
||||||
for(PHCollection *collection in topLevelUserCollections)
|
|
||||||
{
|
|
||||||
if ([collection isKindOfClass:[PHAssetCollection class]])
|
|
||||||
{
|
|
||||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
|
||||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
|
||||||
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
|
||||||
|
|
||||||
//Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
|
|
||||||
|
|
||||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
|
||||||
[userFetchResultArray addObject:assetsFetchResult];
|
|
||||||
[userFetchResultLabel addObject:collection.localizedTitle];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Smart albums: Sorted by descending creation date.
|
|
||||||
NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
|
|
||||||
NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
|
|
||||||
for(PHCollection *collection in smartAlbums)
|
|
||||||
{
|
|
||||||
if ([collection isKindOfClass:[PHAssetCollection class]])
|
|
||||||
{
|
|
||||||
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
|
||||||
|
|
||||||
//Smart collections are PHAssetCollectionType=2;
|
|
||||||
if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
|
|
||||||
{
|
|
||||||
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
|
||||||
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
|
||||||
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
|
||||||
|
|
||||||
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
|
||||||
if(assetsFetchResult.count>0)
|
|
||||||
{
|
|
||||||
[smartFetchResultArray addObject:assetsFetchResult];
|
|
||||||
[smartFetchResultLabel addObject:collection.localizedTitle];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.collectionsFetchResultsAssets= @[allFetchResultArray,smartFetchResultArray,userFetchResultArray];
|
|
||||||
self.collectionsFetchResultsTitles= @[allFetchResultLabel,smartFetchResultLabel,userFetchResultLabel];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Accessors
|
|
||||||
|
|
||||||
- (GMImagePickerController *)picker
|
|
||||||
{
|
|
||||||
return (GMImagePickerController *)self.navigationController.parentViewController;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Rotation
|
|
||||||
|
|
||||||
- (BOOL)shouldAutorotate
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
|
||||||
{
|
|
||||||
return UIInterfaceOrientationMaskAllButUpsideDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - UITableViewDataSource
|
|
||||||
|
|
||||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
|
||||||
{
|
|
||||||
return (NSInteger)self.collectionsFetchResultsAssets.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
|
||||||
return (NSInteger)fetchResult.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
static NSString *CellIdentifier = @"Cell";
|
|
||||||
|
|
||||||
GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
|
||||||
if (cell == nil) {
|
|
||||||
cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
|
||||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the cell's tag
|
|
||||||
NSInteger currentTag = cell.tag + 1;
|
|
||||||
cell.tag = currentTag;
|
|
||||||
|
|
||||||
// Set the label
|
|
||||||
cell.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize];
|
|
||||||
cell.textLabel.text = (self.collectionsFetchResultsTitles[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
|
||||||
cell.textLabel.textColor = self.picker.pickerTextColor;
|
|
||||||
|
|
||||||
// Retrieve the pre-fetched assets for this album:
|
|
||||||
PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
|
||||||
|
|
||||||
// Display the number of assets
|
|
||||||
if (self.picker.displayAlbumsNumberOfAssets) {
|
|
||||||
cell.detailTextLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
|
||||||
cell.detailTextLabel.text = [self tableCellSubtitle:assetsFetchResult];
|
|
||||||
cell.detailTextLabel.textColor = self.picker.pickerTextColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the 3 images (if exists):
|
|
||||||
if ([assetsFetchResult count] > 0) {
|
|
||||||
CGFloat scale = [UIScreen mainScreen].scale;
|
|
||||||
|
|
||||||
//Compute the thumbnail pixel size:
|
|
||||||
CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
|
|
||||||
PHAsset *asset = assetsFetchResult[0];
|
|
||||||
[cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
|
|
||||||
[self.imageManager requestImageForAsset:asset
|
|
||||||
targetSize:tableCellThumbnailSize1
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
cell.imageView1.image = result;
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
// Second & third images:
|
|
||||||
// TODO: Only preload the 3pixels height visible frame!
|
|
||||||
if ([assetsFetchResult count] > 1) {
|
|
||||||
//Compute the thumbnail pixel size:
|
|
||||||
CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
|
|
||||||
PHAsset *asset = assetsFetchResult[1];
|
|
||||||
[self.imageManager requestImageForAsset:asset
|
|
||||||
targetSize:tableCellThumbnailSize2
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
cell.imageView2.image = result;
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
cell.imageView2.image = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([assetsFetchResult count] > 2) {
|
|
||||||
CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
|
|
||||||
PHAsset *asset = assetsFetchResult[2];
|
|
||||||
[self.imageManager requestImageForAsset:asset
|
|
||||||
targetSize:tableCellThumbnailSize3
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
cell.imageView3.image = result;
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
cell.imageView3.image = nil;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[cell setVideoLayout:NO];
|
|
||||||
cell.imageView3.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
|
||||||
cell.imageView2.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
|
||||||
cell.imageView1.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - UITableViewDelegate
|
|
||||||
|
|
||||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
|
||||||
|
|
||||||
// Init the GMGridViewController
|
|
||||||
GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
|
|
||||||
// Set the title
|
|
||||||
gridViewController.title = cell.textLabel.text;
|
|
||||||
// Use the prefetched assets!
|
|
||||||
gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:(NSUInteger)indexPath.section] objectAtIndex:(NSUInteger)indexPath.row];
|
|
||||||
|
|
||||||
// Remove selection so it looks better on slide in
|
|
||||||
[tableView deselectRowAtIndexPath:indexPath animated:true];
|
|
||||||
|
|
||||||
// Push GMGridViewController
|
|
||||||
[self.navigationController pushViewController:gridViewController animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
|
|
||||||
// header.contentView.backgroundColor = [UIColor clearColor];
|
|
||||||
// header.backgroundView.backgroundColor = [UIColor clearColor];
|
|
||||||
|
|
||||||
// Default is a bold font, but keep this styled as a normal font
|
|
||||||
header.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
|
||||||
header.textLabel.textColor = self.picker.pickerTextColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
//Tip: Returning nil hides the section header!
|
|
||||||
|
|
||||||
NSString *title = nil;
|
|
||||||
if (section > 0) {
|
|
||||||
// Only show title for non-empty sections:
|
|
||||||
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
|
||||||
if (fetchResult.count > 0) {
|
|
||||||
title = self.collectionsLocalizedTitles[(NSUInteger)(section - 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - PHPhotoLibraryChangeObserver
|
|
||||||
|
|
||||||
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
|
||||||
{
|
|
||||||
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
|
|
||||||
NSMutableArray *updatedCollectionsFetchResults = nil;
|
|
||||||
|
|
||||||
for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
|
|
||||||
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
|
|
||||||
if (changeDetails) {
|
|
||||||
if (!updatedCollectionsFetchResults) {
|
|
||||||
updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
|
|
||||||
}
|
|
||||||
[updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This only affects to changes in albums level (add/remove/edit album)
|
|
||||||
if (updatedCollectionsFetchResults) {
|
|
||||||
self.collectionsFetchResults = updatedCollectionsFetchResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
// However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
|
|
||||||
// Maybe some checks could be done here , but for now is OKey.
|
|
||||||
[self updateFetchResults];
|
|
||||||
[self.tableView reloadData];
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Cell Subtitle
|
|
||||||
|
|
||||||
- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
|
|
||||||
{
|
|
||||||
// Just return the number of assets. Album app does this:
|
|
||||||
return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
|
@ -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 <UIKit/UIKit.h>
|
|
||||||
#include <Photos/Photos.h>
|
|
||||||
|
|
||||||
|
|
||||||
@interface GMGridViewCell : UICollectionViewCell
|
|
||||||
|
|
||||||
@property (nonatomic, strong) PHAsset *asset;
|
|
||||||
//The imageView
|
|
||||||
@property (nonatomic, strong) UIImageView *imageView;
|
|
||||||
//Video additional information
|
|
||||||
@property (nonatomic, strong) UIImageView *videoIcon;
|
|
||||||
@property (nonatomic, strong) UILabel *videoDuration;
|
|
||||||
@property (nonatomic, strong) UIView *gradientView;
|
|
||||||
@property (nonatomic, strong) CAGradientLayer *gradient;
|
|
||||||
//Selection overlay
|
|
||||||
@property (nonatomic) BOOL shouldShowSelection;
|
|
||||||
@property (nonatomic, strong) UIView *coverView;
|
|
||||||
@property (nonatomic, strong) UIButton *selectedButton;
|
|
||||||
|
|
||||||
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
|
|
||||||
|
|
||||||
- (void)bind:(PHAsset *)asset;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -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
|
|
|
@ -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 <UIKit/UIKit.h>
|
|
||||||
#include <Photos/Photos.h>
|
|
||||||
|
|
||||||
|
|
||||||
@interface GMGridViewController : UICollectionViewController
|
|
||||||
|
|
||||||
@property (strong,nonatomic) PHFetchResult *assetsFetchResults;
|
|
||||||
|
|
||||||
-(id)initWithPicker:(GMImagePickerController *)picker;
|
|
||||||
|
|
||||||
@end
|
|
|
@ -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 <Photos/Photos.h>
|
|
||||||
|
|
||||||
|
|
||||||
//Helper methods
|
|
||||||
@implementation NSIndexSet (Convenience)
|
|
||||||
- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
|
|
||||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
|
|
||||||
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
|
||||||
[indexPaths addObject:[NSIndexPath indexPathForItem:(NSInteger)idx inSection:(NSInteger)section]];
|
|
||||||
}];
|
|
||||||
return indexPaths;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation UICollectionView (Convenience)
|
|
||||||
- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
|
|
||||||
NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
|
|
||||||
if (allLayoutAttributes.count == 0) { return nil; }
|
|
||||||
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
|
|
||||||
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
|
|
||||||
NSIndexPath *indexPath = layoutAttributes.indexPath;
|
|
||||||
[indexPaths addObject:indexPath];
|
|
||||||
}
|
|
||||||
return indexPaths;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@interface GMImagePickerController ()
|
|
||||||
|
|
||||||
- (void)finishPickingAssets:(id)sender;
|
|
||||||
- (void)dismiss:(id)sender;
|
|
||||||
- (NSString *)toolbarTitle;
|
|
||||||
- (UIView *)noAssetsView;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
@interface GMGridViewController () <PHPhotoLibraryChangeObserver>
|
|
||||||
|
|
||||||
@property (nonatomic, weak) GMImagePickerController *picker;
|
|
||||||
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
|
||||||
@property (assign, nonatomic) CGRect previousPreheatRect;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
static CGSize AssetGridThumbnailSize;
|
|
||||||
NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
|
|
||||||
|
|
||||||
@implementation GMGridViewController
|
|
||||||
{
|
|
||||||
CGFloat screenWidth;
|
|
||||||
CGFloat screenHeight;
|
|
||||||
UICollectionViewFlowLayout *portraitLayout;
|
|
||||||
UICollectionViewFlowLayout *landscapeLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
-(id)initWithPicker:(GMImagePickerController *)picker
|
|
||||||
{
|
|
||||||
//Custom init. The picker contains custom information to create the FlowLayout
|
|
||||||
self.picker = picker;
|
|
||||||
|
|
||||||
//Ipad popover is not affected by rotation!
|
|
||||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
|
||||||
{
|
|
||||||
screenWidth = CGRectGetWidth(picker.view.bounds);
|
|
||||||
screenHeight = CGRectGetHeight(picker.view.bounds);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
|
|
||||||
{
|
|
||||||
screenHeight = CGRectGetWidth(picker.view.bounds);
|
|
||||||
screenWidth = CGRectGetHeight(picker.view.bounds);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
screenWidth = CGRectGetWidth(picker.view.bounds);
|
|
||||||
screenHeight = CGRectGetHeight(picker.view.bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
|
|
||||||
if (self = [super initWithCollectionViewLayout:layout])
|
|
||||||
{
|
|
||||||
//Compute the thumbnail pixel size:
|
|
||||||
CGFloat scale = [UIScreen mainScreen].scale;
|
|
||||||
//NSLog(@"This is @%fx scale device", scale);
|
|
||||||
if(scale >= 3)
|
|
||||||
{
|
|
||||||
scale = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
|
||||||
|
|
||||||
self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection;
|
|
||||||
|
|
||||||
[self.collectionView registerClass:GMGridViewCell.class
|
|
||||||
forCellWithReuseIdentifier:GMGridViewCellIdentifier];
|
|
||||||
|
|
||||||
self.preferredContentSize = kPopoverContentSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)viewDidLoad
|
|
||||||
{
|
|
||||||
[super viewDidLoad];
|
|
||||||
[self setupViews];
|
|
||||||
|
|
||||||
// Navigation bar customization
|
|
||||||
if (self.picker.customNavigationBarPrompt) {
|
|
||||||
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imageManager = [[PHCachingImageManager alloc] init];
|
|
||||||
[self resetCachedAssets];
|
|
||||||
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
|
||||||
|
|
||||||
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
|
||||||
{
|
|
||||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewWillAppear:animated];
|
|
||||||
|
|
||||||
[self setupButtons];
|
|
||||||
[self setupToolbar];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewDidAppear:animated];
|
|
||||||
[self updateCachedAssets];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[self resetCachedAssets];
|
|
||||||
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
|
||||||
return self.picker.pickerStatusBarStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Rotation
|
|
||||||
|
|
||||||
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
|
||||||
{
|
|
||||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
|
|
||||||
|
|
||||||
//Update the AssetGridThumbnailSize:
|
|
||||||
CGFloat scale = [UIScreen mainScreen].scale;
|
|
||||||
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
|
||||||
|
|
||||||
[self resetCachedAssets];
|
|
||||||
//This is optional. Reload visible thumbnails:
|
|
||||||
for (GMGridViewCell *cell in [self.collectionView visibleCells]) {
|
|
||||||
NSInteger currentTag = cell.tag;
|
|
||||||
[self.imageManager requestImageForAsset:cell.asset
|
|
||||||
targetSize:AssetGridThumbnailSize
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info)
|
|
||||||
{
|
|
||||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
[cell.imageView setImage:result];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self.collectionView setCollectionViewLayout:layout animated:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Setup
|
|
||||||
|
|
||||||
- (void)setupViews
|
|
||||||
{
|
|
||||||
self.collectionView.backgroundColor = [UIColor clearColor];
|
|
||||||
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setupButtons
|
|
||||||
{
|
|
||||||
if (self.picker.allowsMultipleSelection) {
|
|
||||||
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
|
||||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
|
||||||
style:UIBarButtonItemStyleDone
|
|
||||||
target:self.picker
|
|
||||||
action:@selector(finishPickingAssets:)];
|
|
||||||
|
|
||||||
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
|
||||||
} else {
|
|
||||||
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
|
||||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
|
||||||
style:UIBarButtonItemStyleDone
|
|
||||||
target:self.picker
|
|
||||||
action:@selector(dismiss:)];
|
|
||||||
}
|
|
||||||
if (self.picker.useCustomFontForNavigationBar) {
|
|
||||||
if (self.picker.useCustomFontForNavigationBar) {
|
|
||||||
NSDictionary* barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
|
||||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
|
||||||
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setupToolbar
|
|
||||||
{
|
|
||||||
self.toolbarItems = self.picker.toolbarItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Collection View Layout
|
|
||||||
|
|
||||||
- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
|
|
||||||
{
|
|
||||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
|
||||||
{
|
|
||||||
if(!portraitLayout)
|
|
||||||
{
|
|
||||||
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
|
||||||
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
|
||||||
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing);
|
|
||||||
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
|
||||||
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
|
||||||
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
|
||||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
|
||||||
portraitLayout.minimumLineSpacing = spaceWidth;
|
|
||||||
}
|
|
||||||
return portraitLayout;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(UIInterfaceOrientationIsLandscape(orientation))
|
|
||||||
{
|
|
||||||
if(!landscapeLayout)
|
|
||||||
{
|
|
||||||
landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
|
|
||||||
landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
|
||||||
int cellTotalUsableWidth = (int)(screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing);
|
|
||||||
landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
|
|
||||||
double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
|
|
||||||
double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
|
|
||||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
|
|
||||||
landscapeLayout.minimumLineSpacing = spaceWidth;
|
|
||||||
}
|
|
||||||
return landscapeLayout;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(!portraitLayout)
|
|
||||||
{
|
|
||||||
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
|
||||||
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
|
||||||
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1) * self.picker.minimumInteritemSpacing);
|
|
||||||
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
|
||||||
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
|
||||||
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
|
||||||
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
|
||||||
portraitLayout.minimumLineSpacing = spaceWidth;
|
|
||||||
}
|
|
||||||
return portraitLayout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Collection View Data Source
|
|
||||||
|
|
||||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
|
|
||||||
forIndexPath:indexPath];
|
|
||||||
|
|
||||||
// Increment the cell's tag
|
|
||||||
NSInteger currentTag = cell.tag + 1;
|
|
||||||
cell.tag = currentTag;
|
|
||||||
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
|
||||||
{
|
|
||||||
NSLog(@"Image manager: Requesting FIT image for iPad");
|
|
||||||
[self.imageManager requestImageForAsset:asset
|
|
||||||
targetSize:AssetGridThumbnailSize
|
|
||||||
contentMode:PHImageContentModeAspectFit
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
||||||
|
|
||||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
[cell.imageView setImage:result];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
else*/
|
|
||||||
{
|
|
||||||
//NSLog(@"Image manager: Requesting FILL image for iPhone");
|
|
||||||
[self.imageManager requestImageForAsset:asset
|
|
||||||
targetSize:AssetGridThumbnailSize
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil
|
|
||||||
resultHandler:^(UIImage *result, NSDictionary *info) {
|
|
||||||
|
|
||||||
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
|
||||||
if (cell.tag == currentTag) {
|
|
||||||
[cell.imageView setImage:result];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[cell bind:asset];
|
|
||||||
|
|
||||||
cell.shouldShowSelection = self.picker.allowsMultipleSelection;
|
|
||||||
|
|
||||||
// Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)]) {
|
|
||||||
cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
|
|
||||||
} else {
|
|
||||||
cell.enabled = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting `selected` property blocks further deselection. Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
|
|
||||||
if ([self.picker.selectedAssets containsObject:asset]) {
|
|
||||||
cell.selected = YES;
|
|
||||||
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
|
||||||
} else {
|
|
||||||
cell.selected = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Collection View Delegate
|
|
||||||
|
|
||||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
|
||||||
|
|
||||||
if (!cell.isEnabled) {
|
|
||||||
return NO;
|
|
||||||
} else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)]) {
|
|
||||||
return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
|
|
||||||
} else {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
[self.picker selectAsset:asset];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)]) {
|
|
||||||
[self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) {
|
|
||||||
return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
|
|
||||||
} else {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
[self.picker deselectAsset:asset];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)]) {
|
|
||||||
[self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) {
|
|
||||||
return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
|
|
||||||
} else {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) {
|
|
||||||
[self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
|
||||||
{
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
|
|
||||||
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) {
|
|
||||||
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - UICollectionViewDataSource
|
|
||||||
|
|
||||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
|
||||||
{
|
|
||||||
NSInteger count = (NSInteger)self.assetsFetchResults.count;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - PHPhotoLibraryChangeObserver
|
|
||||||
|
|
||||||
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
|
||||||
{
|
|
||||||
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
|
|
||||||
// check if there are changes to the assets (insertions, deletions, updates)
|
|
||||||
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
|
|
||||||
if (collectionChanges) {
|
|
||||||
|
|
||||||
// get the new fetch result
|
|
||||||
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
|
|
||||||
|
|
||||||
UICollectionView *collectionView = self.collectionView;
|
|
||||||
|
|
||||||
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
|
|
||||||
// we need to reload all if the incremental diffs are not available
|
|
||||||
[collectionView reloadData];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// if we have incremental diffs, tell the collection view to animate insertions and deletions
|
|
||||||
[collectionView performBatchUpdates:^{
|
|
||||||
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
|
|
||||||
if ([removedIndexes count]) {
|
|
||||||
[collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
|
||||||
}
|
|
||||||
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
|
|
||||||
if ([insertedIndexes count]) {
|
|
||||||
[collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
|
||||||
if (self.picker.showCameraButton && self.picker.autoSelectCameraImages) {
|
|
||||||
for (NSIndexPath *path in [insertedIndexes aapl_indexPathsFromIndexesWithSection:0]) {
|
|
||||||
[self collectionView:collectionView didSelectItemAtIndexPath:path];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
|
|
||||||
if ([changedIndexes count]) {
|
|
||||||
[collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
|
||||||
}
|
|
||||||
} completion:NULL];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self resetCachedAssets];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - UIScrollViewDelegate
|
|
||||||
|
|
||||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
|
||||||
{
|
|
||||||
[self updateCachedAssets];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Asset Caching
|
|
||||||
|
|
||||||
- (void)resetCachedAssets
|
|
||||||
{
|
|
||||||
[self.imageManager stopCachingImagesForAllAssets];
|
|
||||||
self.previousPreheatRect = CGRectZero;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateCachedAssets
|
|
||||||
{
|
|
||||||
BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
|
|
||||||
if (!isViewVisible) { return; }
|
|
||||||
|
|
||||||
// The preheat window is twice the height of the visible rect
|
|
||||||
CGRect preheatRect = self.collectionView.bounds;
|
|
||||||
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
|
|
||||||
|
|
||||||
// If scrolled by a "reasonable" amount...
|
|
||||||
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
|
|
||||||
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
|
|
||||||
|
|
||||||
// Compute the assets to start caching and to stop caching.
|
|
||||||
NSMutableArray *addedIndexPaths = [NSMutableArray array];
|
|
||||||
NSMutableArray *removedIndexPaths = [NSMutableArray array];
|
|
||||||
|
|
||||||
[self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
|
|
||||||
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
|
|
||||||
[removedIndexPaths addObjectsFromArray:indexPaths];
|
|
||||||
} addedHandler:^(CGRect addedRect) {
|
|
||||||
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
|
|
||||||
[addedIndexPaths addObjectsFromArray:indexPaths];
|
|
||||||
}];
|
|
||||||
|
|
||||||
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
|
|
||||||
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
|
|
||||||
|
|
||||||
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
|
|
||||||
targetSize:AssetGridThumbnailSize
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil];
|
|
||||||
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
|
|
||||||
targetSize:AssetGridThumbnailSize
|
|
||||||
contentMode:PHImageContentModeAspectFill
|
|
||||||
options:nil];
|
|
||||||
|
|
||||||
self.previousPreheatRect = preheatRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
|
|
||||||
{
|
|
||||||
if (CGRectIntersectsRect(newRect, oldRect)) {
|
|
||||||
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
|
|
||||||
CGFloat oldMinY = CGRectGetMinY(oldRect);
|
|
||||||
CGFloat newMaxY = CGRectGetMaxY(newRect);
|
|
||||||
CGFloat newMinY = CGRectGetMinY(newRect);
|
|
||||||
if (newMaxY > oldMaxY) {
|
|
||||||
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
|
|
||||||
addedHandler(rectToAdd);
|
|
||||||
}
|
|
||||||
if (oldMinY > newMinY) {
|
|
||||||
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
|
|
||||||
addedHandler(rectToAdd);
|
|
||||||
}
|
|
||||||
if (newMaxY < oldMaxY) {
|
|
||||||
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
|
|
||||||
removedHandler(rectToRemove);
|
|
||||||
}
|
|
||||||
if (oldMinY < newMinY) {
|
|
||||||
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
|
|
||||||
removedHandler(rectToRemove);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addedHandler(newRect);
|
|
||||||
removedHandler(oldRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
|
|
||||||
{
|
|
||||||
if (indexPaths.count == 0) { return nil; }
|
|
||||||
|
|
||||||
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
|
|
||||||
for (NSIndexPath *indexPath in indexPaths) {
|
|
||||||
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
|
||||||
[assets addObject:asset];
|
|
||||||
}
|
|
||||||
return assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
|
@ -1,24 +0,0 @@
|
||||||
//
|
|
||||||
// GMImagePicker.h
|
|
||||||
// GMImagePicker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 1/14/19.
|
|
||||||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
//! Project version number for GMImagePicker.
|
|
||||||
FOUNDATION_EXPORT double GMImagePickerVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for GMImagePicker.
|
|
||||||
FOUNDATION_EXPORT const unsigned char GMImagePickerVersionString[];
|
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <GMImagePicker/PublicHeader.h>
|
|
||||||
|
|
||||||
|
|
||||||
#import <GMImagePicker/GMImagePickerController.h>
|
|
||||||
#import <GMImagePicker/GMAlbumsViewCell.h>
|
|
||||||
#import <GMImagePicker/GMAlbumsViewController.h>
|
|
||||||
#import <GMImagePicker/GMGridViewCell.h>
|
|
||||||
#import <GMImagePicker/GMGridViewController.h>
|
|
|
@ -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 <UIKit/UIKit.h>
|
|
||||||
#import <Photos/Photos.h>
|
|
||||||
|
|
||||||
|
|
||||||
//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 <GMImagePickerControllerDelegate> 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 <NSObject>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
|
@ -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 <MobileCoreServices/MobileCoreServices.h>
|
|
||||||
#import "GMImagePickerController.h"
|
|
||||||
#import "GMAlbumsViewController.h"
|
|
||||||
#import <Photos/Photos.h>
|
|
||||||
|
|
||||||
@interface GMImagePickerController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIAlertViewDelegate>
|
|
||||||
|
|
||||||
@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<NSString *,id> *)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
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 158 B |
Binary file not shown.
Before Width: | Height: | Size: 194 B |
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>FMWK</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,7 +3,7 @@
|
||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 50;
|
objectVersion = 52;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
@ -25,9 +25,6 @@
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.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 */; };
|
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
|
||||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
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 */; };
|
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
|
||||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
||||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
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 */; };
|
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
||||||
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; };
|
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; };
|
||||||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.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 */; };
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
|
||||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.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 */; };
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.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 */; };
|
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
|
||||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
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 */; };
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
|
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
|
||||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.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 */; };
|
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
|
||||||
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
||||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.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 */; };
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||||
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; };
|
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, ); }; };
|
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
@ -229,13 +217,6 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = D60A548A21ED515800F1F87C;
|
|
||||||
remoteInfo = GMImagePicker;
|
|
||||||
};
|
|
||||||
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
@ -280,7 +261,6 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */,
|
|
||||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||||
D6BC874621961F73006163F1 /* Gifu.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 = "<group>"; };
|
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
||||||
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
||||||
D60A548B21ED515800F1F87C /* GMImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GMImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
|
||||||
D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
|
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
|
||||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
|
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
|
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
|
||||||
|
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D626493B23C1000300612E6E /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumAssetCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D627943423A5525100D38C68 /* StatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivity.swift; sourceTree = "<group>"; };
|
D627943423A5525100D38C68 /* StatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivity.swift; sourceTree = "<group>"; };
|
||||||
D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
|
D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
|
||||||
|
@ -394,6 +379,7 @@
|
||||||
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
|
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
|
||||||
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
|
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
|
||||||
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
|
||||||
|
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
||||||
|
@ -428,30 +414,6 @@
|
||||||
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
||||||
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
|
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
|
||||||
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||||
D686326F21ED8312008C716E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686327221ED8312008C716E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686327321ED8312008C716E /* GMGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewCell.m; sourceTree = "<group>"; };
|
|
||||||
D686327421ED8312008C716E /* GMVideoIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMVideoIcon@2x.png"; sourceTree = "<group>"; };
|
|
||||||
D686327721ED8313008C716E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686327821ED8313008C716E /* GMAlbumsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewController.m; sourceTree = "<group>"; };
|
|
||||||
D686327921ED8313008C716E /* GMVideoIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMVideoIcon.png; sourceTree = "<group>"; };
|
|
||||||
D686327C21ED8313008C716E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@2x.png"; sourceTree = "<group>"; };
|
|
||||||
D686327E21ED8314008C716E /* GMAlbumsViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewCell.h; sourceTree = "<group>"; };
|
|
||||||
D686327F21ED8315008C716E /* GMGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewController.h; sourceTree = "<group>"; };
|
|
||||||
D686328021ED8315008C716E /* GMSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMSelected.png; sourceTree = "<group>"; };
|
|
||||||
D686328121ED8315008C716E /* GMAlbumsViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewCell.m; sourceTree = "<group>"; };
|
|
||||||
D686328221ED8316008C716E /* GMAlbumsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewController.h; sourceTree = "<group>"; };
|
|
||||||
D686328521ED8316008C716E /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686328821ED8317008C716E /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686328921ED8317008C716E /* GMSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMSelected@2x.png"; sourceTree = "<group>"; };
|
|
||||||
D686328A21ED8317008C716E /* GMImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMImagePickerController.m; sourceTree = "<group>"; };
|
|
||||||
D686328B21ED8317008C716E /* GMGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewCell.h; sourceTree = "<group>"; };
|
|
||||||
D686328E21ED8317008C716E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@1x.png"; sourceTree = "<group>"; };
|
|
||||||
D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewController.m; sourceTree = "<group>"; };
|
|
||||||
D686329121ED8319008C716E /* GMImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMImagePickerController.h; sourceTree = "<group>"; };
|
|
||||||
D686329421ED8319008C716E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = GMImagePicker.strings; sourceTree = "<group>"; };
|
|
||||||
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = "<group>"; };
|
D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtagsManager.swift; sourceTree = "<group>"; };
|
||||||
D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = "<group>"; };
|
D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -480,6 +442,12 @@
|
||||||
D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowAccountActivity.swift; sourceTree = "<group>"; };
|
D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowAccountActivity.swift; sourceTree = "<group>"; };
|
||||||
D6AEBB4723216B1D00E5038B /* AccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivity.swift; sourceTree = "<group>"; };
|
D6AEBB4723216B1D00E5038B /* AccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivity.swift; sourceTree = "<group>"; };
|
||||||
D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowAccountActivity.swift; sourceTree = "<group>"; };
|
D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowAccountActivity.swift; sourceTree = "<group>"; };
|
||||||
|
D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionsListViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AssetCollectionViewCell.xib; sourceTree = "<group>"; };
|
||||||
|
D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerSheetContainerViewController.swift; sourceTree = "<group>"; };
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||||
D6BC874421961F73006163F1 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -549,8 +517,8 @@
|
||||||
files = (
|
files = (
|
||||||
D65F613823AFD65D00F3CFD3 /* Embassy.framework in Frameworks */,
|
D65F613823AFD65D00F3CFD3 /* Embassy.framework in Frameworks */,
|
||||||
D65F613623AFD65900F3CFD3 /* Ambassador.framework in Frameworks */,
|
D65F613623AFD65900F3CFD3 /* Ambassador.framework in Frameworks */,
|
||||||
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */,
|
|
||||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
|
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||||
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
||||||
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
||||||
|
@ -601,39 +569,6 @@
|
||||||
path = Transitions;
|
path = Transitions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
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 = "<group>";
|
|
||||||
};
|
|
||||||
D60C07E221E817560057FAA8 /* Compose Media */ = {
|
D60C07E221E817560057FAA8 /* Compose Media */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -748,6 +683,20 @@
|
||||||
path = "Instance Cell";
|
path = "Instance Cell";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
D627943323A5523800D38C68 /* Status Activities */ = {
|
D627943323A5523800D38C68 /* Status Activities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -889,9 +838,11 @@
|
||||||
D641C787213DD862004B4513 /* Compose */ = {
|
D641C787213DD862004B4513 /* Compose */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
||||||
D627FF77217E94F200CC0648 /* Drafts */,
|
D627FF77217E94F200CC0648 /* Drafts */,
|
||||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||||
|
D626493423BD94CE00612E6E /* CompositionAttachment.swift */,
|
||||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||||
);
|
);
|
||||||
path = Compose;
|
path = Compose;
|
||||||
|
@ -1033,70 +984,6 @@
|
||||||
path = "Compose Status Reply";
|
path = "Compose Status Reply";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D686326D21ED8312008C716E /* es.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686326E21ED8312008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = es.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327021ED8312008C716E /* ca.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686327121ED8312008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = ca.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327521ED8313008C716E /* it.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686327621ED8313008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = it.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327A21ED8313008C716E /* Base.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686327B21ED8313008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = Base.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328321ED8316008C716E /* fr.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686328421ED8316008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = fr.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328621ED8317008C716E /* pt.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686328721ED8317008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = pt.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328C21ED8317008C716E /* en.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686328D21ED8317008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = en.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686329221ED8319008C716E /* de.lproj */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D686329321ED8319008C716E /* GMImagePicker.strings */,
|
|
||||||
);
|
|
||||||
path = de.lproj;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1174,6 +1061,19 @@
|
||||||
path = "Account Activities";
|
path = "Account Activities";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
D6BC9DD8232D8BCA002CA326 /* Search */ = {
|
D6BC9DD8232D8BCA002CA326 /* Search */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1196,6 +1096,7 @@
|
||||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||||
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
||||||
D60C07E221E817560057FAA8 /* Compose Media */,
|
D60C07E221E817560057FAA8 /* Compose Media */,
|
||||||
|
D626494023C122C800612E6E /* Asset Picker */,
|
||||||
D641C78A213DD926004B4513 /* Status */,
|
D641C78A213DD926004B4513 /* Status */,
|
||||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||||
D641C78B213DD92F004B4513 /* Profile Header */,
|
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||||
|
@ -1238,7 +1139,6 @@
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
||||||
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||||
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
||||||
D60A548C21ED515800F1F87C /* GMImagePicker */,
|
|
||||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||||
|
@ -1333,12 +1233,6 @@
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1422,9 +1316,11 @@
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||||
D60A549121ED515800F1F87C /* PBXTargetDependency */,
|
|
||||||
);
|
);
|
||||||
name = Tusker;
|
name = Tusker;
|
||||||
|
packageProductDependencies = (
|
||||||
|
D6B0539E23BD2BA300A066FA /* SheetController */,
|
||||||
|
);
|
||||||
productName = Tusker;
|
productName = Tusker;
|
||||||
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
|
@ -1521,6 +1417,7 @@
|
||||||
);
|
);
|
||||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
||||||
);
|
);
|
||||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
@ -1541,20 +1438,6 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1578,16 +1461,20 @@
|
||||||
files = (
|
files = (
|
||||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||||
|
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||||
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
||||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */,
|
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */,
|
||||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */,
|
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */,
|
||||||
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
||||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
||||||
|
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */,
|
||||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||||
|
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */,
|
||||||
|
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||||
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
||||||
|
@ -1644,11 +1531,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1713,9 +1595,11 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */,
|
||||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||||
|
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */,
|
||||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */,
|
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */,
|
||||||
|
@ -1754,6 +1638,7 @@
|
||||||
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */,
|
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */,
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||||
|
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||||
D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */,
|
D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */,
|
||||||
|
@ -1768,6 +1653,7 @@
|
||||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
||||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||||
|
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
||||||
|
@ -1779,11 +1665,15 @@
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */,
|
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */,
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||||
|
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
|
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
|
||||||
|
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||||
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
|
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
|
||||||
|
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||||
|
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
||||||
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
||||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
||||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
|
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
|
||||||
|
@ -1817,12 +1707,14 @@
|
||||||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
||||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,
|
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,
|
||||||
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */,
|
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */,
|
||||||
|
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */,
|
||||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
||||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||||
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
|
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
|
||||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
||||||
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */,
|
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */,
|
||||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||||
|
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */,
|
||||||
D6163F2C21AA0AF1008DAC41 /* MyProfileTableViewController.swift in Sources */,
|
D6163F2C21AA0AF1008DAC41 /* MyProfileTableViewController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -1851,11 +1743,6 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
D60A549121ED515800F1F87C /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = D60A548A21ED515800F1F87C /* GMImagePicker */;
|
|
||||||
targetProxy = D60A549021ED515800F1F87C /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||||
|
@ -1884,70 +1771,6 @@
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
D686326E21ED8312008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686326F21ED8312008C716E /* es */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327121ED8312008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686327221ED8312008C716E /* ca */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327621ED8313008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686327721ED8313008C716E /* it */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686327B21ED8313008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686327C21ED8313008C716E /* Base */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328421ED8316008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686328521ED8316008C716E /* fr */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328721ED8317008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686328821ED8317008C716E /* pt */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686328D21ED8317008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686328E21ED8317008C716E /* en */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D686329321ED8319008C716E /* GMImagePicker.strings */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
D686329421ED8319008C716E /* de */,
|
|
||||||
);
|
|
||||||
name = GMImagePicker.strings;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
|
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2426,6 +2249,25 @@
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* 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 */;
|
rootObject = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -83,8 +83,7 @@ extension DraftsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DraftAttachment: Codable {
|
struct DraftAttachment: Codable {
|
||||||
/// The local identifier of the PHAsset for this attachment
|
let attachment: CompositionAttachment
|
||||||
let assetIdentifier: String
|
|
||||||
let description: String
|
let description: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,10 @@
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Post videos from the camera.</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Post photos from the camera.</string>
|
<string>Post photos and videos from the camera.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>Save photos directly from other people's posts.</string>
|
<string>Save photos directly from other people's posts.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
|
|
@ -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<PHAsset> {
|
||||||
|
return PHAsset.fetchAssets(in: collection, options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<PHAsset>!
|
||||||
|
|
||||||
|
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<PHAsset> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Section, Item>()
|
||||||
|
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<Section, Item> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,9 +9,6 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Intents
|
import Intents
|
||||||
import Photos
|
|
||||||
import GMImagePicker
|
|
||||||
import MobileCoreServices
|
|
||||||
|
|
||||||
class ComposeViewController: UIViewController {
|
class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
|
@ -28,7 +25,7 @@ class ComposeViewController: UIViewController {
|
||||||
visibilityChanged()
|
visibilityChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var selectedAssets: [PHAsset] = [] {
|
var selectedAttachments: [CompositionAttachment] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
updateAttachmentViews()
|
updateAttachmentViews()
|
||||||
}
|
}
|
||||||
|
@ -303,7 +300,7 @@ class ComposeViewController: UIViewController {
|
||||||
case .pleroma:
|
case .pleroma:
|
||||||
addAttachmentButton.isEnabled = true
|
addAttachmentButton.isEnabled = true
|
||||||
case .mastodon:
|
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()
|
let mediaView = ComposeMediaView.create()
|
||||||
mediaView.delegate = self
|
mediaView.delegate = self
|
||||||
mediaView.update(asset: asset)
|
mediaView.update(attachment: attachment)
|
||||||
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
||||||
updateAddAttachmentButton()
|
updateAddAttachmentButton()
|
||||||
}
|
}
|
||||||
|
@ -339,12 +336,11 @@ class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
func saveDraft() {
|
func saveDraft() {
|
||||||
var attachments = [DraftsManager.DraftAttachment]()
|
var attachments = [DraftsManager.DraftAttachment]()
|
||||||
for asset in selectedAssets {
|
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews
|
||||||
let index = attachments.count
|
where mediaView.attachment.canSaveToDraft {
|
||||||
let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView
|
let attachment = mediaView.attachment!
|
||||||
let description = mediaView.descriptionTextView.text!
|
let description = mediaView.descriptionTextView.text ?? ""
|
||||||
|
attachments.append(.init(attachment: attachment, description: description))
|
||||||
attachments.append(DraftsManager.DraftAttachment(assetIdentifier: asset.localIdentifier, description: description))
|
|
||||||
}
|
}
|
||||||
let cw = contentWarningEnabled ? contentWarningTextField.text : nil
|
let cw = contentWarningEnabled ? contentWarningTextField.text : nil
|
||||||
if let currentDraft = self.currentDraft {
|
if let currentDraft = self.currentDraft {
|
||||||
|
@ -360,13 +356,6 @@ class ComposeViewController: UIViewController {
|
||||||
xcbSession?.complete(with: .cancel)
|
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
|
// MARK: - Interaction
|
||||||
|
|
||||||
@objc func showSaveAndClosePrompt() {
|
@objc func showSaveAndClosePrompt() {
|
||||||
|
@ -448,12 +437,13 @@ class ComposeViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func addAttachmentPressed(_ sender: Any) {
|
@IBAction func addAttachmentPressed(_ sender: Any) {
|
||||||
let picker = GMImagePickerController()
|
// hide keyboard before showing asset picker, so it doesn't re-appear when asset picker is closed
|
||||||
picker.delegate = self
|
contentWarningTextField.resignFirstResponder()
|
||||||
picker.toolbarTintColor = view.tintColor
|
statusTextView.resignFirstResponder()
|
||||||
picker.navigationBarTintColor = view.tintColor
|
|
||||||
picker.title = "Choose Attachment"
|
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||||
present(picker, animated: true)
|
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||||
|
present(sheetContainer, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func postButtonPressed() {
|
@objc func postButtonPressed() {
|
||||||
|
@ -478,7 +468,7 @@ class ComposeViewController: UIViewController {
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
var attachments: [Attachment?] = []
|
var attachments: [Attachment?] = []
|
||||||
for asset in selectedAssets {
|
for compAttachment in selectedAttachments {
|
||||||
let index = attachments.count
|
let index = attachments.count
|
||||||
attachments.append(nil)
|
attachments.append(nil)
|
||||||
|
|
||||||
|
@ -487,26 +477,7 @@ class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
group.enter()
|
group.enter()
|
||||||
|
|
||||||
let options = PHImageRequestOptions()
|
compAttachment.getData { (data, mimeType) in
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
self.postProgressView.step()
|
self.postProgressView.step()
|
||||||
|
|
||||||
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||||
|
@ -588,30 +559,29 @@ extension ComposeViewController: UITextViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeViewController: GMImagePickerControllerDelegate {
|
extension ComposeViewController: AssetPickerViewControllerDelegate {
|
||||||
func assetsPickerController(_ picker: GMImagePickerController!, didFinishPickingAssets assets: [Any]!) {
|
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool {
|
||||||
let assets = assets as! [PHAsset]
|
|
||||||
selectedAssets.append(contentsOf: assets)
|
|
||||||
picker.dismiss(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assetsPickerController(_ picker: GMImagePickerController!, shouldSelect asset: PHAsset!) -> Bool {
|
|
||||||
switch MastodonController.instance.instanceType {
|
switch MastodonController.instance.instanceType {
|
||||||
case .pleroma:
|
case .pleroma:
|
||||||
return true
|
return true
|
||||||
case .mastodon:
|
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 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 {
|
extension ComposeViewController: ComposeMediaViewDelegate {
|
||||||
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
||||||
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
||||||
selectedAssets.remove(at: index)
|
selectedAttachments.remove(at: index)
|
||||||
updateAddAttachmentButton()
|
updateAddAttachmentButton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -651,21 +621,11 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
||||||
updatePlaceholder()
|
updatePlaceholder()
|
||||||
updateCharactersRemaining()
|
updateCharactersRemaining()
|
||||||
|
|
||||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil)
|
selectedAttachments = draft.attachments.map { $0.attachment }
|
||||||
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 }
|
|
||||||
updateAttachmentViews()
|
updateAttachmentViews()
|
||||||
|
|
||||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews {
|
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
|
mediaView.descriptionTextView.text = attachment.description
|
||||||
|
|
||||||
// call the delegate method manually, since setting the text property doesn't call it
|
// call the delegate method manually, since setting the text property doesn't call it
|
||||||
|
@ -675,10 +635,10 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
||||||
|
|
||||||
func draftSelectionCompleted() {
|
func draftSelectionCompleted() {
|
||||||
// check that all the assets from the draft have been added
|
// 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
|
// 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
|
// todo: localize me
|
||||||
let suffix = difference == 1 ? "" : "s"
|
let suffix = difference == 1 ? "" : "s"
|
||||||
let verb = difference == 1 ? "was" : "were"
|
let verb = difference == 1 ? "was" : "were"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,12 +25,8 @@ class DraftTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
attachmentsStackViewContainer.isHidden = draft.attachments.count == 0
|
attachmentsStackViewContainer.isHidden = draft.attachments.count == 0
|
||||||
|
|
||||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil)
|
for attachment in draft.attachments {
|
||||||
var i = 0
|
|
||||||
while i < result.count {
|
|
||||||
let asset = result[i]
|
|
||||||
let size = CGSize(width: 50, height: 50)
|
let size = CGSize(width: 50, height: 50)
|
||||||
|
|
||||||
let imageView = UIImageView(frame: CGRect(origin: .zero, size: size))
|
let imageView = UIImageView(frame: CGRect(origin: .zero, size: size))
|
||||||
imageView.contentMode = .scaleAspectFill
|
imageView.contentMode = .scaleAspectFill
|
||||||
imageView.layer.masksToBounds = true
|
imageView.layer.masksToBounds = true
|
||||||
|
@ -38,10 +34,17 @@ class DraftTableViewCell: UITableViewCell {
|
||||||
attachmentsStackView.addArrangedSubview(imageView)
|
attachmentsStackView.addArrangedSubview(imageView)
|
||||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
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
|
imageView.image = image
|
||||||
|
case .video(_):
|
||||||
|
// videos aren't saved to drafts, so this is unreachable
|
||||||
|
return
|
||||||
}
|
}
|
||||||
i += 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="80" id="LwN-cu-e1a" customClass="AlbumTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="LwN-cu-e1a" id="QLt-HB-gOJ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="cXr-fV-sDO">
|
||||||
|
<rect key="frame" x="16" y="8" width="64" height="64"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="64" id="6u9-0z-pGV"/>
|
||||||
|
<constraint firstAttribute="height" constant="64" id="ZhL-aE-TCF"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Album Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fK1-aD-yvs">
|
||||||
|
<rect key="frame" x="96" y="30" width="216" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="cXr-fV-sDO" firstAttribute="top" secondItem="QLt-HB-gOJ" secondAttribute="top" constant="8" id="8GX-vc-jvD"/>
|
||||||
|
<constraint firstItem="fK1-aD-yvs" firstAttribute="centerY" secondItem="QLt-HB-gOJ" secondAttribute="centerY" id="AAh-V8-OJ7"/>
|
||||||
|
<constraint firstItem="cXr-fV-sDO" firstAttribute="leading" secondItem="QLt-HB-gOJ" secondAttribute="leading" constant="16" id="Gz0-fU-nKd"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="fK1-aD-yvs" secondAttribute="trailing" constant="8" id="Pe6-jo-KIA"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="cXr-fV-sDO" secondAttribute="bottom" constant="8" id="xJj-Dg-1FO"/>
|
||||||
|
<constraint firstItem="fK1-aD-yvs" firstAttribute="leading" secondItem="cXr-fV-sDO" secondAttribute="trailing" constant="16" id="yj6-kK-SUS"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<viewLayoutGuide key="safeArea" id="dfZ-83-hfN"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="albumTitleLabel" destination="fK1-aD-yvs" id="QRf-gV-JJ7"/>
|
||||||
|
<outlet property="thumbnailImageView" destination="cXr-fV-sDO" id="7b8-cp-q1r"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="131.8840579710145" y="165.40178571428569"/>
|
||||||
|
</tableViewCell>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="80" id="KGk-i7-Jjw" customClass="AllPhotosTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="320" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="v0o-e0-yXS">
|
||||||
|
<rect key="frame" x="16" y="8" width="64" height="64"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="64" id="8YD-mM-wwC"/>
|
||||||
|
<constraint firstAttribute="width" constant="64" id="P7K-i4-Id7"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="All Photos" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pcI-Ow-ilI">
|
||||||
|
<rect key="frame" x="96" y="30" width="216" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="v0o-e0-yXS" secondAttribute="bottom" constant="8" id="Ibx-xb-EmO"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="pcI-Ow-ilI" secondAttribute="trailing" constant="8" id="TbR-p1-oE0"/>
|
||||||
|
<constraint firstItem="v0o-e0-yXS" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="Z5p-M5-wRa"/>
|
||||||
|
<constraint firstItem="pcI-Ow-ilI" firstAttribute="centerY" secondItem="H2p-sc-9uM" secondAttribute="centerY" id="jHF-7S-lOe"/>
|
||||||
|
<constraint firstItem="pcI-Ow-ilI" firstAttribute="leading" secondItem="v0o-e0-yXS" secondAttribute="trailing" constant="16" id="nYa-2g-plY"/>
|
||||||
|
<constraint firstItem="v0o-e0-yXS" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="orl-S0-nOM"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="thumbnailImageView" destination="v0o-e0-yXS" id="tzf-v9-W5A"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="131.8840579710145" y="165.40178571428569"/>
|
||||||
|
</tableViewCell>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="AssetCollectionViewCell" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="WTV-P3-i1p">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||||
|
</imageView>
|
||||||
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YII-hi-HYM">
|
||||||
|
<rect key="frame" x="52" y="4" width="24" height="24"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="qDM-OF-sru">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="checkmark.circle.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="QVi-7M-ZCZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.5" width="24" height="23"/>
|
||||||
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="QVi-7M-ZCZ" secondAttribute="trailing" id="H6U-pA-do1"/>
|
||||||
|
<constraint firstItem="QVi-7M-ZCZ" firstAttribute="leading" secondItem="qDM-OF-sru" secondAttribute="leading" id="jLt-7y-7dg"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="QVi-7M-ZCZ" secondAttribute="bottom" id="tdC-nt-Cnj"/>
|
||||||
|
<constraint firstItem="QVi-7M-ZCZ" firstAttribute="top" secondItem="qDM-OF-sru" secondAttribute="top" id="z1v-3z-ajN"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="24" id="ed0-Y0-KjW"/>
|
||||||
|
<constraint firstAttribute="width" constant="24" id="nuX-3h-u88"/>
|
||||||
|
</constraints>
|
||||||
|
<blurEffect style="regular"/>
|
||||||
|
</visualEffectView>
|
||||||
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Hlz-Ev-VVC">
|
||||||
|
<rect key="frame" x="4" y="4" width="24" height="24"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="rji-hd-WaI">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="livephoto" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="8rw-ON-ITF">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="24" height="23.5"/>
|
||||||
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="8rw-ON-ITF" firstAttribute="leading" secondItem="rji-hd-WaI" secondAttribute="leading" id="Ffy-3B-pRp"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="8rw-ON-ITF" secondAttribute="bottom" id="QBe-Ya-Q8e"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="8rw-ON-ITF" secondAttribute="trailing" id="cto-10-8tH"/>
|
||||||
|
<constraint firstItem="8rw-ON-ITF" firstAttribute="top" secondItem="rji-hd-WaI" secondAttribute="top" id="xEl-0b-jrg"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="24" id="hE2-1Y-uld"/>
|
||||||
|
<constraint firstAttribute="width" constant="24" id="vto-89-Z5S"/>
|
||||||
|
</constraints>
|
||||||
|
<blurEffect style="regular"/>
|
||||||
|
</visualEffectView>
|
||||||
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IIZ-GM-E3u">
|
||||||
|
<rect key="frame" x="40" y="54" width="36" height="22"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="OUW-gn-6QT">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="36" height="22"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ssL-HM-AWE">
|
||||||
|
<rect key="frame" x="2" y="2" width="32" height="18"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" secondItem="ssL-HM-AWE" secondAttribute="height" constant="4" id="Ho2-xo-Qj4"/>
|
||||||
|
<constraint firstItem="ssL-HM-AWE" firstAttribute="centerY" secondItem="OUW-gn-6QT" secondAttribute="centerY" id="ZP7-1L-gQD"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="ssL-HM-AWE" secondAttribute="width" constant="4" id="l6s-tM-JiK"/>
|
||||||
|
<constraint firstItem="ssL-HM-AWE" firstAttribute="centerX" secondItem="OUW-gn-6QT" secondAttribute="centerX" id="mW7-hA-jwe"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<blurEffect style="regular"/>
|
||||||
|
</visualEffectView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="WTV-P3-i1p" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="CEL-x8-BTT"/>
|
||||||
|
<constraint firstItem="Hlz-Ev-VVC" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="4" id="E9r-xN-KzP"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="YII-hi-HYM" secondAttribute="trailing" constant="4" id="JHt-mg-f9H"/>
|
||||||
|
<constraint firstItem="WTV-P3-i1p" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="JgX-RF-Ez0"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="IIZ-GM-E3u" secondAttribute="trailing" constant="4" id="ck6-5X-0L6"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="WTV-P3-i1p" secondAttribute="bottom" id="qYB-uR-2la"/>
|
||||||
|
<constraint firstItem="Hlz-Ev-VVC" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="4" id="r51-3B-QwE"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="WTV-P3-i1p" secondAttribute="trailing" id="uU7-y7-P2N"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="IIZ-GM-E3u" secondAttribute="bottom" constant="4" id="yUe-tE-wfx"/>
|
||||||
|
<constraint firstItem="YII-hi-HYM" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="4" id="zFa-AC-7zM"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="checkmarkVisualEffectView" destination="YII-hi-HYM" id="BRd-xO-aRf"/>
|
||||||
|
<outlet property="durationLabel" destination="ssL-HM-AWE" id="VWw-SA-UZl"/>
|
||||||
|
<outlet property="durationVisualEffectView" destination="IIZ-GM-E3u" id="se0-iB-8Kc"/>
|
||||||
|
<outlet property="imageView" destination="WTV-P3-i1p" id="eMG-lU-fUF"/>
|
||||||
|
<outlet property="livePhotoVisualEffectView" destination="Hlz-Ev-VVC" id="Y4J-Nv-dIr"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="132" y="154"/>
|
||||||
|
</collectionViewCell>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="checkmark.circle.fill" catalog="system" width="64" height="60"/>
|
||||||
|
<image name="livephoto" catalog="system" width="64" height="60"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="camera" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="fUY-i2-EtY">
|
||||||
|
<rect key="frame" x="16" y="16.5" width="48" height="46"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="fUY-i2-EtY" secondAttribute="bottom" constant="16" id="HkO-Cn-2Na"/>
|
||||||
|
<constraint firstItem="fUY-i2-EtY" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="16" id="bUq-pX-5XL"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="fUY-i2-EtY" secondAttribute="trailing" constant="16" id="fyg-fN-FJu"/>
|
||||||
|
<constraint firstItem="fUY-i2-EtY" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="16" id="oOU-Tc-Z5T"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
|
||||||
|
<point key="canvasLocation" x="132" y="154"/>
|
||||||
|
</collectionViewCell>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="camera" catalog="system" width="64" height="48"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Photos
|
import Photos
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
protocol ComposeMediaViewDelegate {
|
protocol ComposeMediaViewDelegate {
|
||||||
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||||
|
@ -21,7 +22,7 @@ class ComposeMediaView: UIView {
|
||||||
@IBOutlet weak var descriptionTextView: UITextView!
|
@IBOutlet weak var descriptionTextView: UITextView!
|
||||||
@IBOutlet weak var placeholderLabel: UILabel!
|
@IBOutlet weak var placeholderLabel: UILabel!
|
||||||
|
|
||||||
var assetIdentifier: String?
|
var attachment: CompositionAttachment!
|
||||||
|
|
||||||
static func create() -> ComposeMediaView {
|
static func create() -> ComposeMediaView {
|
||||||
return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! 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
|
descriptionTextView.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(asset: PHAsset) {
|
func update(attachment: CompositionAttachment) {
|
||||||
self.assetIdentifier = asset.localIdentifier
|
self.attachment = attachment
|
||||||
|
|
||||||
let size = CGSize(width: 80, height: 80)
|
switch attachment {
|
||||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
case let .image(image):
|
||||||
self.imageView.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue