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;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 52;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
@ -25,9 +25,6 @@
|
|||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
||||
D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; };
|
||||
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||
|
@ -76,6 +73,13 @@
|
|||
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
|
||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
||||
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
|
||||
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; };
|
||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; };
|
||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; };
|
||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493B23C1000300612E6E /* AlbumTableViewCell.xib */; };
|
||||
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */; };
|
||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
||||
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; };
|
||||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; };
|
||||
|
@ -109,6 +113,7 @@
|
|||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
|
||||
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
|
||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
|
||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
|
||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
||||
|
@ -142,30 +147,6 @@
|
|||
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
||||
D686329521ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686326E21ED8312008C716E /* GMImagePicker.strings */; };
|
||||
D686329621ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327121ED8312008C716E /* GMImagePicker.strings */; };
|
||||
D686329721ED8319008C716E /* GMGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327321ED8312008C716E /* GMGridViewCell.m */; };
|
||||
D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327421ED8312008C716E /* GMVideoIcon@2x.png */; };
|
||||
D686329921ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327621ED8313008C716E /* GMImagePicker.strings */; };
|
||||
D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327821ED8313008C716E /* GMAlbumsViewController.m */; };
|
||||
D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327921ED8313008C716E /* GMVideoIcon.png */; };
|
||||
D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327B21ED8313008C716E /* GMImagePicker.strings */; };
|
||||
D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */; };
|
||||
D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327E21ED8314008C716E /* GMAlbumsViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D686329F21ED8319008C716E /* GMGridViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327F21ED8315008C716E /* GMGridViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A021ED8319008C716E /* GMSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328021ED8315008C716E /* GMSelected.png */; };
|
||||
D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328121ED8315008C716E /* GMAlbumsViewCell.m */; };
|
||||
D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328221ED8316008C716E /* GMAlbumsViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328421ED8316008C716E /* GMImagePicker.strings */; };
|
||||
D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328721ED8317008C716E /* GMImagePicker.strings */; };
|
||||
D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328921ED8317008C716E /* GMSelected@2x.png */; };
|
||||
D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328A21ED8317008C716E /* GMImagePickerController.m */; };
|
||||
D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328B21ED8317008C716E /* GMGridViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328D21ED8317008C716E /* GMImagePicker.strings */; };
|
||||
D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */; };
|
||||
D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; };
|
||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
|
||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
|
||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
|
||||
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; };
|
||||
|
@ -194,6 +175,13 @@
|
|||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
|
||||
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
|
||||
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */; };
|
||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0539E23BD2BA300A066FA /* SheetController */; };
|
||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; };
|
||||
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; };
|
||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; };
|
||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
|
||||
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
|
||||
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */; };
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; };
|
||||
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
|
@ -229,13 +217,6 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D60A548A21ED515800F1F87C;
|
||||
remoteInfo = GMImagePicker;
|
||||
};
|
||||
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
|
@ -280,7 +261,6 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */,
|
||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||
D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */,
|
||||
|
@ -311,8 +291,6 @@
|
|||
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<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; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -362,6 +340,13 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -394,6 +379,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -428,30 +414,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -480,6 +442,12 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -549,8 +517,8 @@
|
|||
files = (
|
||||
D65F613823AFD65D00F3CFD3 /* Embassy.framework in Frameworks */,
|
||||
D65F613623AFD65900F3CFD3 /* Ambassador.framework in Frameworks */,
|
||||
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */,
|
||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||
D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */,
|
||||
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
|
||||
|
@ -601,39 +569,6 @@
|
|||
path = Transitions;
|
||||
sourceTree = "<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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -748,6 +683,20 @@
|
|||
path = "Instance Cell";
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -889,9 +838,11 @@
|
|||
D641C787213DD862004B4513 /* Compose */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6B053A023BD2BED00A066FA /* Asset Picker */,
|
||||
D627FF77217E94F200CC0648 /* Drafts */,
|
||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||
D626493423BD94CE00612E6E /* CompositionAttachment.swift */,
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
|
@ -1033,70 +984,6 @@
|
|||
path = "Compose Status Reply";
|
||||
sourceTree = "<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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1174,6 +1061,19 @@
|
|||
path = "Account Activities";
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1196,6 +1096,7 @@
|
|||
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
||||
D60C07E221E817560057FAA8 /* Compose Media */,
|
||||
D626494023C122C800612E6E /* Asset Picker */,
|
||||
D641C78A213DD926004B4513 /* Status */,
|
||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||
|
@ -1238,7 +1139,6 @@
|
|||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
||||
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
||||
D60A548C21ED515800F1F87C /* GMImagePicker */,
|
||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||
|
@ -1333,12 +1233,6 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329F21ED8319008C716E /* GMGridViewController.h in Headers */,
|
||||
D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */,
|
||||
D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */,
|
||||
D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */,
|
||||
D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */,
|
||||
D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1422,9 +1316,11 @@
|
|||
);
|
||||
dependencies = (
|
||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
D60A549121ED515800F1F87C /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tusker;
|
||||
packageProductDependencies = (
|
||||
D6B0539E23BD2BA300A066FA /* SheetController */,
|
||||
);
|
||||
productName = Tusker;
|
||||
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
@ -1521,6 +1417,7 @@
|
|||
);
|
||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||
packageReferences = (
|
||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
|
||||
);
|
||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -1541,20 +1438,6 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329621ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */,
|
||||
D686329521ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */,
|
||||
D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329921ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */,
|
||||
D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */,
|
||||
D68632A021ED8319008C716E /* GMSelected.png in Resources */,
|
||||
D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */,
|
||||
D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1578,16 +1461,20 @@
|
|||
files = (
|
||||
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */,
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */,
|
||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */,
|
||||
D667E5E921349EE50057A976 /* ProfileHeaderTableViewCell.xib in Resources */,
|
||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */,
|
||||
D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */,
|
||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */,
|
||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||
0411610122B442870030A9B7 /* AttachmentViewController.xib in Resources */,
|
||||
|
@ -1644,11 +1531,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */,
|
||||
D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */,
|
||||
D686329721ED8319008C716E /* GMGridViewCell.m in Sources */,
|
||||
D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */,
|
||||
D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1713,9 +1595,11 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */,
|
||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||
D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */,
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */,
|
||||
|
@ -1754,6 +1638,7 @@
|
|||
0454DDB122B467AA00B8BB8E /* GalleryShrinkAnimationController.swift in Sources */,
|
||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||
D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */,
|
||||
|
@ -1768,6 +1653,7 @@
|
|||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
|
||||
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
|
||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||
D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */,
|
||||
D627944A23A6AD6100D38C68 /* BookmarksTableViewController.swift in Sources */,
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */,
|
||||
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
|
||||
|
@ -1779,11 +1665,15 @@
|
|||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
|
||||
D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */,
|
||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
|
||||
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
|
||||
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
|
||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
||||
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
|
||||
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
|
||||
|
@ -1817,12 +1707,14 @@
|
|||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */,
|
||||
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */,
|
||||
D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */,
|
||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
|
||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
|
||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
||||
D667E5E721349D4C0057A976 /* ProfileTableViewController.swift in Sources */,
|
||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||
D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */,
|
||||
D6163F2C21AA0AF1008DAC41 /* MyProfileTableViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1851,11 +1743,6 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
D60A549121ED515800F1F87C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D60A548A21ED515800F1F87C /* GMImagePicker */;
|
||||
targetProxy = D60A549021ED515800F1F87C /* PBXContainerItemProxy */;
|
||||
};
|
||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||
|
@ -1884,70 +1771,6 @@
|
|||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
D686326E21ED8312008C716E /* GMImagePicker.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
D686326F21ED8312008C716E /* es */,
|
||||
);
|
||||
name = GMImagePicker.strings;
|
||||
sourceTree = "<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 */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
@ -2426,6 +2249,25 @@
|
|||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://git.shadowfacts.net/shadowfacts/SheetController.git";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D6B0539E23BD2BA300A066FA /* SheetController */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
||||
productName = SheetController;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
/// The local identifier of the PHAsset for this attachment
|
||||
let assetIdentifier: String
|
||||
let attachment: CompositionAttachment
|
||||
let description: String
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,8 +52,10 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Post videos from the camera.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Post photos from the camera.</string>
|
||||
<string>Post photos and videos from the camera.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<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 Pachyderm
|
||||
import Intents
|
||||
import Photos
|
||||
import GMImagePicker
|
||||
import MobileCoreServices
|
||||
|
||||
class ComposeViewController: UIViewController {
|
||||
|
||||
|
@ -28,7 +25,7 @@ class ComposeViewController: UIViewController {
|
|||
visibilityChanged()
|
||||
}
|
||||
}
|
||||
var selectedAssets: [PHAsset] = [] {
|
||||
var selectedAttachments: [CompositionAttachment] = [] {
|
||||
didSet {
|
||||
updateAttachmentViews()
|
||||
}
|
||||
|
@ -303,7 +300,7 @@ class ComposeViewController: UIViewController {
|
|||
case .pleroma:
|
||||
addAttachmentButton.isEnabled = true
|
||||
case .mastodon:
|
||||
addAttachmentButton.isEnabled = selectedAssets.count <= 4 && selectedAssets.first(where: { $0.mediaType == .video }) == nil
|
||||
addAttachmentButton.isEnabled = selectedAttachments.count <= 4 && !selectedAttachments.contains(where: { $0.type == .video })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,10 +311,10 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
for asset in selectedAssets {
|
||||
for attachment in selectedAttachments {
|
||||
let mediaView = ComposeMediaView.create()
|
||||
mediaView.delegate = self
|
||||
mediaView.update(asset: asset)
|
||||
mediaView.update(attachment: attachment)
|
||||
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
||||
updateAddAttachmentButton()
|
||||
}
|
||||
|
@ -339,12 +336,11 @@ class ComposeViewController: UIViewController {
|
|||
|
||||
func saveDraft() {
|
||||
var attachments = [DraftsManager.DraftAttachment]()
|
||||
for asset in selectedAssets {
|
||||
let index = attachments.count
|
||||
let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView
|
||||
let description = mediaView.descriptionTextView.text!
|
||||
|
||||
attachments.append(DraftsManager.DraftAttachment(assetIdentifier: asset.localIdentifier, description: description))
|
||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews
|
||||
where mediaView.attachment.canSaveToDraft {
|
||||
let attachment = mediaView.attachment!
|
||||
let description = mediaView.descriptionTextView.text ?? ""
|
||||
attachments.append(.init(attachment: attachment, description: description))
|
||||
}
|
||||
let cw = contentWarningEnabled ? contentWarningTextField.text : nil
|
||||
if let currentDraft = self.currentDraft {
|
||||
|
@ -360,13 +356,6 @@ class ComposeViewController: UIViewController {
|
|||
xcbSession?.complete(with: .cancel)
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
statusTextView.resignFirstResponder()
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func showSaveAndClosePrompt() {
|
||||
|
@ -448,12 +437,13 @@ class ComposeViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction func addAttachmentPressed(_ sender: Any) {
|
||||
let picker = GMImagePickerController()
|
||||
picker.delegate = self
|
||||
picker.toolbarTintColor = view.tintColor
|
||||
picker.navigationBarTintColor = view.tintColor
|
||||
picker.title = "Choose Attachment"
|
||||
present(picker, animated: true)
|
||||
// hide keyboard before showing asset picker, so it doesn't re-appear when asset picker is closed
|
||||
contentWarningTextField.resignFirstResponder()
|
||||
statusTextView.resignFirstResponder()
|
||||
|
||||
let sheetContainer = AssetPickerSheetContainerViewController()
|
||||
sheetContainer.assetPicker.assetPickerDelegate = self
|
||||
present(sheetContainer, animated: true)
|
||||
}
|
||||
|
||||
@objc func postButtonPressed() {
|
||||
|
@ -478,7 +468,7 @@ class ComposeViewController: UIViewController {
|
|||
let group = DispatchGroup()
|
||||
|
||||
var attachments: [Attachment?] = []
|
||||
for asset in selectedAssets {
|
||||
for compAttachment in selectedAttachments {
|
||||
let index = attachments.count
|
||||
attachments.append(nil)
|
||||
|
||||
|
@ -487,26 +477,7 @@ class ComposeViewController: UIViewController {
|
|||
|
||||
group.enter()
|
||||
|
||||
let options = PHImageRequestOptions()
|
||||
options.version = .current
|
||||
options.deliveryMode = .highQualityFormat
|
||||
options.resizeMode = .none
|
||||
options.isNetworkAccessAllowed = true
|
||||
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, dataUTI, orientation, info) in
|
||||
guard var data = data, let dataUTI = dataUTI else { fatalError() }
|
||||
|
||||
let mimeType: String
|
||||
if dataUTI == "public.heic" {
|
||||
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
|
||||
let image = CIImage(data: data)!
|
||||
let context = CIContext()
|
||||
let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
|
||||
data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])!
|
||||
mimeType = "image/jpeg"
|
||||
} else {
|
||||
mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String
|
||||
}
|
||||
|
||||
compAttachment.getData { (data, mimeType) in
|
||||
self.postProgressView.step()
|
||||
|
||||
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||
|
@ -588,30 +559,29 @@ extension ComposeViewController: UITextViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: GMImagePickerControllerDelegate {
|
||||
func assetsPickerController(_ picker: GMImagePickerController!, didFinishPickingAssets assets: [Any]!) {
|
||||
let assets = assets as! [PHAsset]
|
||||
selectedAssets.append(contentsOf: assets)
|
||||
picker.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func assetsPickerController(_ picker: GMImagePickerController!, shouldSelect asset: PHAsset!) -> Bool {
|
||||
extension ComposeViewController: AssetPickerViewControllerDelegate {
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachment.AttachmentType) -> Bool {
|
||||
switch MastodonController.instance.instanceType {
|
||||
case .pleroma:
|
||||
return true
|
||||
case .mastodon:
|
||||
if (asset.mediaType == .video && selectedAssets.count > 0) || selectedAssets.first(where: { $0.mediaType == .video }) != nil {
|
||||
if (type == .video && selectedAttachments.count > 0) ||
|
||||
selectedAttachments.contains(where: { $0.type == .video }) ||
|
||||
assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) {
|
||||
return false
|
||||
}
|
||||
return selectedAssets.count + picker.selectedAssets.count < 4
|
||||
return selectedAttachments.count + assetPicker.currentCollectionSelectedAssets.count < 4
|
||||
}
|
||||
}
|
||||
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) {
|
||||
selectedAttachments.append(contentsOf: attachments)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeViewController: ComposeMediaViewDelegate {
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
||||
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
||||
selectedAssets.remove(at: index)
|
||||
selectedAttachments.remove(at: index)
|
||||
updateAddAttachmentButton()
|
||||
}
|
||||
}
|
||||
|
@ -651,21 +621,11 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|||
updatePlaceholder()
|
||||
updateCharactersRemaining()
|
||||
|
||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil)
|
||||
var assets = [String: (asset: PHAsset, description: String)]()
|
||||
var addedAssets = 0
|
||||
while addedAssets < result.count {
|
||||
let asset = result[addedAssets]
|
||||
let attachment = draft.attachments.first(where: { $0.assetIdentifier == asset.localIdentifier })!
|
||||
assets[asset.localIdentifier] = (asset, attachment.description)
|
||||
addedAssets += 1
|
||||
}
|
||||
|
||||
self.selectedAssets = assets.values.map { $0.asset }
|
||||
selectedAttachments = draft.attachments.map { $0.attachment }
|
||||
updateAttachmentViews()
|
||||
|
||||
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews {
|
||||
let attachment = draft.attachments.first(where: { $0.assetIdentifier == mediaView.assetIdentifier })!
|
||||
let attachment = draft.attachments.first(where: { $0.attachment == mediaView.attachment })!
|
||||
mediaView.descriptionTextView.text = attachment.description
|
||||
|
||||
// call the delegate method manually, since setting the text property doesn't call it
|
||||
|
@ -675,10 +635,10 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|||
|
||||
func draftSelectionCompleted() {
|
||||
// check that all the assets from the draft have been added
|
||||
if let currentDraft = currentDraft, selectedAssets.count < currentDraft.attachments.count {
|
||||
if let currentDraft = currentDraft, selectedAttachments.count < currentDraft.attachments.count {
|
||||
// some of the assets in the draft weren't loaded, so notify the user
|
||||
|
||||
let difference = currentDraft.attachments.count - selectedAssets.count
|
||||
let difference = currentDraft.attachments.count - selectedAttachments.count
|
||||
// todo: localize me
|
||||
let suffix = difference == 1 ? "" : "s"
|
||||
let verb = difference == 1 ? "was" : "were"
|
||||
|
|
|
@ -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
|
||||
|
||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: draft.attachments.map { $0.assetIdentifier }, options: nil)
|
||||
var i = 0
|
||||
while i < result.count {
|
||||
let asset = result[i]
|
||||
for attachment in draft.attachments {
|
||||
let size = CGSize(width: 50, height: 50)
|
||||
|
||||
let imageView = UIImageView(frame: CGRect(origin: .zero, size: size))
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.masksToBounds = true
|
||||
|
@ -38,10 +34,17 @@ class DraftTableViewCell: UITableViewCell {
|
|||
attachmentsStackView.addArrangedSubview(imageView)
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
|
||||
switch attachment.attachment {
|
||||
case let .asset(asset):
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
imageView.image = image
|
||||
}
|
||||
i += 1
|
||||
case let .image(image):
|
||||
imageView.image = image
|
||||
case .video(_):
|
||||
// videos aren't saved to drafts, so this is unreachable
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 Photos
|
||||
import AVFoundation
|
||||
|
||||
protocol ComposeMediaViewDelegate {
|
||||
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||
|
@ -21,7 +22,7 @@ class ComposeMediaView: UIView {
|
|||
@IBOutlet weak var descriptionTextView: UITextView!
|
||||
@IBOutlet weak var placeholderLabel: UILabel!
|
||||
|
||||
var assetIdentifier: String?
|
||||
var attachment: CompositionAttachment!
|
||||
|
||||
static func create() -> ComposeMediaView {
|
||||
return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeMediaView
|
||||
|
@ -36,13 +37,25 @@ class ComposeMediaView: UIView {
|
|||
descriptionTextView.delegate = self
|
||||
}
|
||||
|
||||
func update(asset: PHAsset) {
|
||||
self.assetIdentifier = asset.localIdentifier
|
||||
func update(attachment: CompositionAttachment) {
|
||||
self.attachment = attachment
|
||||
|
||||
switch attachment {
|
||||
case let .image(image):
|
||||
imageView.image = image
|
||||
case let .asset(asset):
|
||||
let size = CGSize(width: 80, height: 80)
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
guard self.attachment == attachment else { return }
|
||||
self.imageView.image = image
|
||||
}
|
||||
case let .video(url):
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
imageView.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
|
Loading…
Reference in New Issue