Initial rewrite/redesign of compose screen
Still pending: - Posting videos - Image resizing - Attachment drawer thing
This commit is contained in:
parent
5b3a3d9729
commit
b8430be00c
Binary file not shown.
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// GMAlbumsViewCell.h
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 22/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <UIKit/UIKit.h>
|
||||||
|
#include <Photos/Photos.h>
|
||||||
|
|
||||||
|
@interface GMAlbumsViewCell : UITableViewCell
|
||||||
|
|
||||||
|
@property (strong) PHFetchResult *assetsFetchResults;
|
||||||
|
@property (strong) PHAssetCollection *assetCollection;
|
||||||
|
|
||||||
|
//The labels
|
||||||
|
@property (nonatomic, strong) UILabel *titleLabel;
|
||||||
|
@property (nonatomic, strong) UILabel *infoLabel;
|
||||||
|
//The imageView
|
||||||
|
@property (nonatomic, strong) UIImageView *imageView1;
|
||||||
|
@property (nonatomic, strong) UIImageView *imageView2;
|
||||||
|
@property (nonatomic, strong) UIImageView *imageView3;
|
||||||
|
//Video additional information
|
||||||
|
@property (nonatomic, strong) UIImageView *videoIcon;
|
||||||
|
@property (nonatomic, strong) UIImageView *slowMoIcon;
|
||||||
|
@property (nonatomic, strong) UIView *gradientView;
|
||||||
|
@property (nonatomic, strong) CAGradientLayer *gradient;
|
||||||
|
//Selection overlay
|
||||||
|
|
||||||
|
- (void)setVideoLayout:(BOOL)isVideo;
|
||||||
|
@end
|
|
@ -0,0 +1,131 @@
|
||||||
|
//
|
||||||
|
// GMAlbumsViewCell.m
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 22/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GMAlbumsViewCell.h"
|
||||||
|
#import "GMAlbumsViewController.h"
|
||||||
|
#import "GMImagePickerController.h"
|
||||||
|
#import <QuartzCore/QuartzCore.h>
|
||||||
|
|
||||||
|
@implementation GMAlbumsViewCell
|
||||||
|
|
||||||
|
- (void)awakeFromNib
|
||||||
|
{
|
||||||
|
[super awakeFromNib];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
|
||||||
|
{
|
||||||
|
|
||||||
|
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
|
||||||
|
{
|
||||||
|
// self.isAccessibilityElement = YES;
|
||||||
|
self.contentView.backgroundColor = [UIColor clearColor];
|
||||||
|
self.backgroundColor = [UIColor clearColor];
|
||||||
|
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||||
|
|
||||||
|
// Border width of 1 pixel:
|
||||||
|
float borderWidth = 1.0/[UIScreen mainScreen].scale;
|
||||||
|
|
||||||
|
// ImageView
|
||||||
|
_imageView3 = [UIImageView new];
|
||||||
|
_imageView3.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
|
||||||
|
[_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||||
|
[_imageView3.layer setBorderWidth: borderWidth];
|
||||||
|
_imageView3.clipsToBounds = YES;
|
||||||
|
_imageView3.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
_imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||||
|
[self.contentView addSubview:_imageView3];
|
||||||
|
|
||||||
|
// ImageView
|
||||||
|
_imageView2 = [UIImageView new];
|
||||||
|
_imageView2.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
|
||||||
|
[_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||||
|
[_imageView2.layer setBorderWidth: borderWidth];
|
||||||
|
_imageView2.clipsToBounds = YES;
|
||||||
|
_imageView2.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
_imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||||
|
[self.contentView addSubview:_imageView2];
|
||||||
|
|
||||||
|
// ImageView
|
||||||
|
_imageView1 = [UIImageView new];
|
||||||
|
_imageView1.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
|
||||||
|
[_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
|
||||||
|
[_imageView1.layer setBorderWidth: borderWidth];
|
||||||
|
_imageView1.clipsToBounds = YES;
|
||||||
|
_imageView1.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
_imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||||
|
[self.contentView addSubview:_imageView1];
|
||||||
|
|
||||||
|
|
||||||
|
// The video gradient, label & icon
|
||||||
|
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
|
||||||
|
UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
|
||||||
|
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
|
||||||
|
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
|
||||||
|
_gradient = [CAGradientLayer layer];
|
||||||
|
_gradient.frame = _gradientView.bounds;
|
||||||
|
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
|
||||||
|
_gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
|
||||||
|
[_gradientView.layer insertSublayer:_gradient atIndex:0];
|
||||||
|
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||||
|
_gradientView.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
[self.imageView1 addSubview:_gradientView];
|
||||||
|
_gradientView.hidden = YES;
|
||||||
|
|
||||||
|
// VideoIcon
|
||||||
|
_videoIcon = [UIImageView new];
|
||||||
|
_videoIcon.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
|
||||||
|
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMAlbumsViewCell.class] compatibleWithTraitCollection:nil];
|
||||||
|
_videoIcon.clipsToBounds = YES;
|
||||||
|
_videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
|
||||||
|
[self.imageView1 addSubview:_videoIcon];
|
||||||
|
_videoIcon.hidden = NO;
|
||||||
|
|
||||||
|
// TextLabel
|
||||||
|
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
|
||||||
|
self.textLabel.numberOfLines = 1;
|
||||||
|
|
||||||
|
self.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
|
||||||
|
self.detailTextLabel.numberOfLines = 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)layoutSubviews {
|
||||||
|
[super layoutSubviews];
|
||||||
|
self.textLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.textLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8, self.textLabel.frame.size.height);
|
||||||
|
self.detailTextLabel.frame = CGRectMake(kAlbumLeftToImageSpace + kAlbumThumbnailSize1.width + kAlbumImageToTextSpace,self.detailTextLabel.frame.origin.y,self.contentView.frame.size.width - kAlbumLeftToImageSpace - kAlbumThumbnailSize1.width - 8 - kAlbumImageToTextSpace, self.detailTextLabel.frame.size.height);
|
||||||
|
|
||||||
|
}
|
||||||
|
- (void)setVideoLayout:(BOOL)isVideo
|
||||||
|
{
|
||||||
|
// TODO : Add additional icons for slowmo, burst, etc...
|
||||||
|
if (isVideo) {
|
||||||
|
_videoIcon.hidden = NO;
|
||||||
|
_gradientView.hidden = NO;
|
||||||
|
} else {
|
||||||
|
_videoIcon.hidden = YES;
|
||||||
|
_gradientView.hidden = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
|
||||||
|
{
|
||||||
|
[super setSelected:selected animated:animated];
|
||||||
|
|
||||||
|
// Configure the view for the selected state
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// GMAlbumsViewController.h
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
// Measuring IOS8 Photos APP at @2x (iPhone5s):
|
||||||
|
// The rows are 180px/90pts
|
||||||
|
// Left image border is 21px/10.5pts
|
||||||
|
// Separation between image and text is 42px/21pts (double the previouse one)
|
||||||
|
// The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
|
||||||
|
// The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
||||||
|
// The third image measures 123px/61.5pts including 1px/0.5pts white border. Only 3px/1.5pts visible
|
||||||
|
|
||||||
|
static int kAlbumRowHeight = 90;
|
||||||
|
static int kAlbumLeftToImageSpace = 10;
|
||||||
|
static int kAlbumImageToTextSpace = 21;
|
||||||
|
static float const kAlbumGradientHeight = 20.0f;
|
||||||
|
static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
|
||||||
|
static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
|
||||||
|
static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMAlbumsViewController : UITableViewController
|
||||||
|
|
||||||
|
- (void)selectAllAlbumsCell;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,416 @@
|
||||||
|
//
|
||||||
|
// GMAlbumsViewController.m
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GMImagePickerController.h"
|
||||||
|
#import "GMAlbumsViewController.h"
|
||||||
|
#import "GMGridViewCell.h"
|
||||||
|
#import "GMGridViewController.h"
|
||||||
|
#import "GMAlbumsViewCell.h"
|
||||||
|
|
||||||
|
#include <Photos/Photos.h>
|
||||||
|
|
||||||
|
@interface GMAlbumsViewController() <PHPhotoLibraryChangeObserver>
|
||||||
|
|
||||||
|
@property (strong,nonatomic) NSArray *collectionsFetchResults;
|
||||||
|
@property (strong,nonatomic) NSArray *collectionsLocalizedTitles;
|
||||||
|
@property (strong,nonatomic) NSArray *collectionsFetchResultsAssets;
|
||||||
|
@property (strong,nonatomic) NSArray *collectionsFetchResultsTitles;
|
||||||
|
@property (nonatomic, weak) GMImagePickerController *picker;
|
||||||
|
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GMAlbumsViewController
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
if (self = [super initWithStyle:UITableViewStylePlain]) {
|
||||||
|
self.preferredContentSize = kPopoverContentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *const AllPhotosReuseIdentifier = @"AllPhotosCell";
|
||||||
|
static NSString *const CollectionCellReuseIdentifier = @"CollectionCell";
|
||||||
|
|
||||||
|
- (void)viewDidLoad
|
||||||
|
{
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
||||||
|
|
||||||
|
// Navigation bar customization
|
||||||
|
if (self.picker.customNavigationBarPrompt) {
|
||||||
|
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imageManager = [[PHCachingImageManager alloc] init];
|
||||||
|
|
||||||
|
// Table view aspect
|
||||||
|
self.tableView.rowHeight = kAlbumRowHeight;
|
||||||
|
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
NSDictionary *barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
||||||
|
|
||||||
|
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
||||||
|
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
||||||
|
style:UIBarButtonItemStylePlain
|
||||||
|
target:self.picker
|
||||||
|
action:@selector(dismiss:)];
|
||||||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||||||
|
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||||
|
[self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.picker.allowsMultipleSelection) {
|
||||||
|
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
||||||
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
||||||
|
style:UIBarButtonItemStyleDone
|
||||||
|
target:self.picker
|
||||||
|
action:@selector(finishPickingAssets:)];
|
||||||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||||||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom toolbar
|
||||||
|
self.toolbarItems = self.picker.toolbarItems;
|
||||||
|
|
||||||
|
// Title
|
||||||
|
if (!self.picker.title) {
|
||||||
|
self.title = NSLocalizedStringFromTableInBundle(@"picker.navigation.title", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Navigation bar default title");
|
||||||
|
} else {
|
||||||
|
self.title = self.picker.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch PHAssetCollections:
|
||||||
|
PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
|
||||||
|
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
|
||||||
|
self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
|
||||||
|
self.collectionsLocalizedTitles = @[NSLocalizedStringFromTableInBundle(@"picker.table.smart-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Smart Albums"),NSLocalizedStringFromTableInBundle(@"picker.table.user-albums-header", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Albums")];
|
||||||
|
|
||||||
|
[self updateFetchResults];
|
||||||
|
|
||||||
|
// Register for changes
|
||||||
|
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
||||||
|
|
||||||
|
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
||||||
|
{
|
||||||
|
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||||
|
return self.picker.pickerStatusBarStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)selectAllAlbumsCell {
|
||||||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
|
||||||
|
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)updateFetchResults
|
||||||
|
{
|
||||||
|
//What I do here is fetch both the albums list and the assets of each album.
|
||||||
|
//This way I have acces to the number of items in each album, I can load the 3
|
||||||
|
//thumbnails directly and I can pass the fetched result to the gridViewController.
|
||||||
|
|
||||||
|
self.collectionsFetchResultsAssets=nil;
|
||||||
|
self.collectionsFetchResultsTitles=nil;
|
||||||
|
|
||||||
|
//Fetch PHAssetCollections:
|
||||||
|
PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
|
||||||
|
PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
|
||||||
|
|
||||||
|
//All album: Sorted by descending creation date.
|
||||||
|
NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
|
||||||
|
NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
|
||||||
|
{
|
||||||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||||
|
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||||||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
|
||||||
|
[allFetchResultArray addObject:assetsFetchResult];
|
||||||
|
[allFetchResultLabel addObject:NSLocalizedStringFromTableInBundle(@"picker.table.all-photos-label", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"All photos")];
|
||||||
|
}
|
||||||
|
|
||||||
|
//User albums:
|
||||||
|
NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
|
||||||
|
NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
|
||||||
|
for(PHCollection *collection in topLevelUserCollections)
|
||||||
|
{
|
||||||
|
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||||||
|
{
|
||||||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||||
|
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||||||
|
|
||||||
|
//Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
|
||||||
|
|
||||||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||||||
|
[userFetchResultArray addObject:assetsFetchResult];
|
||||||
|
[userFetchResultLabel addObject:collection.localizedTitle];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Smart albums: Sorted by descending creation date.
|
||||||
|
NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
|
||||||
|
NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
|
||||||
|
for(PHCollection *collection in smartAlbums)
|
||||||
|
{
|
||||||
|
if ([collection isKindOfClass:[PHAssetCollection class]])
|
||||||
|
{
|
||||||
|
PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
|
||||||
|
|
||||||
|
//Smart collections are PHAssetCollectionType=2;
|
||||||
|
if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
|
||||||
|
{
|
||||||
|
PHFetchOptions *options = [[PHFetchOptions alloc] init];
|
||||||
|
options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
|
||||||
|
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
|
||||||
|
|
||||||
|
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
|
||||||
|
if(assetsFetchResult.count>0)
|
||||||
|
{
|
||||||
|
[smartFetchResultArray addObject:assetsFetchResult];
|
||||||
|
[smartFetchResultLabel addObject:collection.localizedTitle];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collectionsFetchResultsAssets= @[allFetchResultArray,smartFetchResultArray,userFetchResultArray];
|
||||||
|
self.collectionsFetchResultsTitles= @[allFetchResultLabel,smartFetchResultLabel,userFetchResultLabel];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Accessors
|
||||||
|
|
||||||
|
- (GMImagePickerController *)picker
|
||||||
|
{
|
||||||
|
return (GMImagePickerController *)self.navigationController.parentViewController;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Rotation
|
||||||
|
|
||||||
|
- (BOOL)shouldAutorotate
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||||
|
{
|
||||||
|
return UIInterfaceOrientationMaskAllButUpsideDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDataSource
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||||
|
{
|
||||||
|
return (NSInteger)self.collectionsFetchResultsAssets.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||||||
|
return (NSInteger)fetchResult.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
static NSString *CellIdentifier = @"Cell";
|
||||||
|
|
||||||
|
GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
|
||||||
|
if (cell == nil) {
|
||||||
|
cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
|
||||||
|
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the cell's tag
|
||||||
|
NSInteger currentTag = cell.tag + 1;
|
||||||
|
cell.tag = currentTag;
|
||||||
|
|
||||||
|
// Set the label
|
||||||
|
cell.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize];
|
||||||
|
cell.textLabel.text = (self.collectionsFetchResultsTitles[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||||||
|
cell.textLabel.textColor = self.picker.pickerTextColor;
|
||||||
|
|
||||||
|
// Retrieve the pre-fetched assets for this album:
|
||||||
|
PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
|
||||||
|
|
||||||
|
// Display the number of assets
|
||||||
|
if (self.picker.displayAlbumsNumberOfAssets) {
|
||||||
|
cell.detailTextLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||||||
|
cell.detailTextLabel.text = [self tableCellSubtitle:assetsFetchResult];
|
||||||
|
cell.detailTextLabel.textColor = self.picker.pickerTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the 3 images (if exists):
|
||||||
|
if ([assetsFetchResult count] > 0) {
|
||||||
|
CGFloat scale = [UIScreen mainScreen].scale;
|
||||||
|
|
||||||
|
//Compute the thumbnail pixel size:
|
||||||
|
CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
|
||||||
|
PHAsset *asset = assetsFetchResult[0];
|
||||||
|
[cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
|
||||||
|
[self.imageManager requestImageForAsset:asset
|
||||||
|
targetSize:tableCellThumbnailSize1
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
cell.imageView1.image = result;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Second & third images:
|
||||||
|
// TODO: Only preload the 3pixels height visible frame!
|
||||||
|
if ([assetsFetchResult count] > 1) {
|
||||||
|
//Compute the thumbnail pixel size:
|
||||||
|
CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
|
||||||
|
PHAsset *asset = assetsFetchResult[1];
|
||||||
|
[self.imageManager requestImageForAsset:asset
|
||||||
|
targetSize:tableCellThumbnailSize2
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
cell.imageView2.image = result;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
cell.imageView2.image = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([assetsFetchResult count] > 2) {
|
||||||
|
CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
|
||||||
|
PHAsset *asset = assetsFetchResult[2];
|
||||||
|
[self.imageManager requestImageForAsset:asset
|
||||||
|
targetSize:tableCellThumbnailSize3
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
cell.imageView3.image = result;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
cell.imageView3.image = nil;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
[cell setVideoLayout:NO];
|
||||||
|
cell.imageView3.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||||
|
cell.imageView2.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||||
|
cell.imageView1.image = [UIImage imageNamed:@"GMEmptyFolder"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDelegate
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
|
||||||
|
|
||||||
|
// Init the GMGridViewController
|
||||||
|
GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
|
||||||
|
// Set the title
|
||||||
|
gridViewController.title = cell.textLabel.text;
|
||||||
|
// Use the prefetched assets!
|
||||||
|
gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:(NSUInteger)indexPath.section] objectAtIndex:(NSUInteger)indexPath.row];
|
||||||
|
|
||||||
|
// Remove selection so it looks better on slide in
|
||||||
|
[tableView deselectRowAtIndexPath:indexPath animated:true];
|
||||||
|
|
||||||
|
// Push GMGridViewController
|
||||||
|
[self.navigationController pushViewController:gridViewController animated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
|
||||||
|
// header.contentView.backgroundColor = [UIColor clearColor];
|
||||||
|
// header.backgroundView.backgroundColor = [UIColor clearColor];
|
||||||
|
|
||||||
|
// Default is a bold font, but keep this styled as a normal font
|
||||||
|
header.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
|
||||||
|
header.textLabel.textColor = self.picker.pickerTextColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
//Tip: Returning nil hides the section header!
|
||||||
|
|
||||||
|
NSString *title = nil;
|
||||||
|
if (section > 0) {
|
||||||
|
// Only show title for non-empty sections:
|
||||||
|
PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
|
||||||
|
if (fetchResult.count > 0) {
|
||||||
|
title = self.collectionsLocalizedTitles[(NSUInteger)(section - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - PHPhotoLibraryChangeObserver
|
||||||
|
|
||||||
|
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
||||||
|
{
|
||||||
|
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
|
NSMutableArray *updatedCollectionsFetchResults = nil;
|
||||||
|
|
||||||
|
for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
|
||||||
|
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
|
||||||
|
if (changeDetails) {
|
||||||
|
if (!updatedCollectionsFetchResults) {
|
||||||
|
updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
|
||||||
|
}
|
||||||
|
[updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This only affects to changes in albums level (add/remove/edit album)
|
||||||
|
if (updatedCollectionsFetchResults) {
|
||||||
|
self.collectionsFetchResults = updatedCollectionsFetchResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
|
||||||
|
// Maybe some checks could be done here , but for now is OKey.
|
||||||
|
[self updateFetchResults];
|
||||||
|
[self.tableView reloadData];
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Cell Subtitle
|
||||||
|
|
||||||
|
- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
|
||||||
|
{
|
||||||
|
// Just return the number of assets. Album app does this:
|
||||||
|
return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// GMGridViewCell.h
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <UIKit/UIKit.h>
|
||||||
|
#include <Photos/Photos.h>
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMGridViewCell : UICollectionViewCell
|
||||||
|
|
||||||
|
@property (nonatomic, strong) PHAsset *asset;
|
||||||
|
//The imageView
|
||||||
|
@property (nonatomic, strong) UIImageView *imageView;
|
||||||
|
//Video additional information
|
||||||
|
@property (nonatomic, strong) UIImageView *videoIcon;
|
||||||
|
@property (nonatomic, strong) UILabel *videoDuration;
|
||||||
|
@property (nonatomic, strong) UIView *gradientView;
|
||||||
|
@property (nonatomic, strong) CAGradientLayer *gradient;
|
||||||
|
//Selection overlay
|
||||||
|
@property (nonatomic) BOOL shouldShowSelection;
|
||||||
|
@property (nonatomic, strong) UIView *coverView;
|
||||||
|
@property (nonatomic, strong) UIButton *selectedButton;
|
||||||
|
|
||||||
|
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
|
||||||
|
|
||||||
|
- (void)bind:(PHAsset *)asset;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,176 @@
|
||||||
|
//
|
||||||
|
// GMGridViewCell.m
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GMGridViewCell.h"
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMGridViewCell ()
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GMGridViewCell
|
||||||
|
|
||||||
|
static UIFont *titleFont;
|
||||||
|
static CGFloat titleHeight;
|
||||||
|
static UIImage *videoIcon;
|
||||||
|
static UIColor *titleColor;
|
||||||
|
static UIImage *checkedIcon;
|
||||||
|
static UIColor *selectedColor;
|
||||||
|
static UIColor *disabledColor;
|
||||||
|
|
||||||
|
+ (void)initialize
|
||||||
|
{
|
||||||
|
titleFont = [UIFont systemFontOfSize:12];
|
||||||
|
titleHeight = 20.0f;
|
||||||
|
videoIcon = [UIImage imageNamed:@"GMImagePickerVideo"];
|
||||||
|
titleColor = [UIColor whiteColor];
|
||||||
|
checkedIcon = [UIImage imageNamed:@"CTAssetsPickerChecked"];
|
||||||
|
selectedColor = [UIColor colorWithWhite:1 alpha:0.3];
|
||||||
|
disabledColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)awakeFromNib
|
||||||
|
{
|
||||||
|
[super awakeFromNib];
|
||||||
|
|
||||||
|
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||||
|
self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithFrame:(CGRect)frame
|
||||||
|
{
|
||||||
|
if (self = [super initWithFrame:frame]) {
|
||||||
|
self.opaque = NO;
|
||||||
|
self.enabled = YES;
|
||||||
|
|
||||||
|
CGFloat cellSize = self.contentView.bounds.size.width;
|
||||||
|
|
||||||
|
// The image view
|
||||||
|
_imageView = [UIImageView new];
|
||||||
|
_imageView.frame = CGRectMake(0, 0, cellSize, cellSize);
|
||||||
|
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||||
|
{
|
||||||
|
_imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
}*/
|
||||||
|
_imageView.clipsToBounds = YES;
|
||||||
|
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
_imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||||
|
[self addSubview:_imageView];
|
||||||
|
|
||||||
|
|
||||||
|
// The video gradient, label & icon
|
||||||
|
float x_offset = 4.0f;
|
||||||
|
UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
|
||||||
|
UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.8];
|
||||||
|
_gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, self.bounds.size.height-titleHeight, self.bounds.size.width, titleHeight)];
|
||||||
|
_gradient = [CAGradientLayer layer];
|
||||||
|
_gradient.frame = _gradientView.bounds;
|
||||||
|
_gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[botGradient CGColor], nil];
|
||||||
|
[_gradientView.layer insertSublayer:_gradient atIndex:0];
|
||||||
|
_gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
||||||
|
_gradientView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
[self addSubview:_gradientView];
|
||||||
|
_gradientView.hidden = YES;
|
||||||
|
|
||||||
|
_videoIcon = [UIImageView new];
|
||||||
|
_videoIcon.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
|
||||||
|
_videoIcon.contentMode = UIViewContentModeLeft;
|
||||||
|
_videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil];
|
||||||
|
_videoIcon.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
_videoIcon.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
|
||||||
|
[self addSubview:_videoIcon];
|
||||||
|
_videoIcon.hidden = YES;
|
||||||
|
|
||||||
|
_videoDuration = [UILabel new];
|
||||||
|
_videoDuration.font = titleFont;
|
||||||
|
_videoDuration.textColor = titleColor;
|
||||||
|
_videoDuration.textAlignment = NSTextAlignmentRight;
|
||||||
|
_videoDuration.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
|
||||||
|
_videoDuration.contentMode = UIViewContentModeRight;
|
||||||
|
_videoDuration.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
_videoDuration.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
|
||||||
|
[self addSubview:_videoDuration];
|
||||||
|
_videoDuration.hidden = YES;
|
||||||
|
|
||||||
|
// Selection overlay & icon
|
||||||
|
_coverView = [[UIView alloc] initWithFrame:self.bounds];
|
||||||
|
_coverView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
_coverView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||||
|
_coverView.backgroundColor = [UIColor colorWithRed:0.24 green:0.47 blue:0.85 alpha:0.6];
|
||||||
|
[self addSubview:_coverView];
|
||||||
|
_coverView.hidden = YES;
|
||||||
|
|
||||||
|
_selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
_selectedButton.frame = CGRectMake(2*self.bounds.size.width/3, 0*self.bounds.size.width/3, self.bounds.size.width/3, self.bounds.size.width/3);
|
||||||
|
_selectedButton.contentMode = UIViewContentModeTopRight;
|
||||||
|
_selectedButton.adjustsImageWhenHighlighted = NO;
|
||||||
|
[_selectedButton setImage:nil forState:UIControlStateNormal];
|
||||||
|
_selectedButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
|
_selectedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||||
|
[_selectedButton setImage:[UIImage imageNamed:@"GMSelected" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil] forState:UIControlStateSelected];
|
||||||
|
_selectedButton.hidden = NO;
|
||||||
|
_selectedButton.userInteractionEnabled = NO;
|
||||||
|
[self addSubview:_selectedButton];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the views above are created in case this is toggled per cell, on the fly, etc.!
|
||||||
|
self.shouldShowSelection = YES;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required to resize the CAGradientLayer because it does not support auto resizing.
|
||||||
|
- (void)layoutSubviews {
|
||||||
|
[super layoutSubviews];
|
||||||
|
_gradient.frame = _gradientView.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)bind:(PHAsset *)asset
|
||||||
|
{
|
||||||
|
self.asset = asset;
|
||||||
|
|
||||||
|
if (self.asset.mediaType == PHAssetMediaTypeVideo) {
|
||||||
|
_videoIcon.hidden = NO;
|
||||||
|
_videoDuration.hidden = NO;
|
||||||
|
_gradientView.hidden = NO;
|
||||||
|
_videoDuration.text = [self getDurationWithFormat:self.asset.duration];
|
||||||
|
} else {
|
||||||
|
_videoIcon.hidden = YES;
|
||||||
|
_videoDuration.hidden = YES;
|
||||||
|
_gradientView.hidden = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override setSelected
|
||||||
|
- (void)setSelected:(BOOL)selected
|
||||||
|
{
|
||||||
|
[super setSelected:selected];
|
||||||
|
|
||||||
|
if (!self.shouldShowSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_coverView.hidden = !selected;
|
||||||
|
_selectedButton.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString*)getDurationWithFormat:(NSTimeInterval)duration
|
||||||
|
{
|
||||||
|
NSInteger ti = (NSInteger)duration;
|
||||||
|
NSInteger seconds = ti % 60;
|
||||||
|
NSInteger minutes = (ti / 60) % 60;
|
||||||
|
//NSInteger hours = (ti / 3600);
|
||||||
|
return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// GMGridViewController.h
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#import "GMImagePickerController.h"
|
||||||
|
#include <UIKit/UIKit.h>
|
||||||
|
#include <Photos/Photos.h>
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMGridViewController : UICollectionViewController
|
||||||
|
|
||||||
|
@property (strong,nonatomic) PHFetchResult *assetsFetchResults;
|
||||||
|
|
||||||
|
-(id)initWithPicker:(GMImagePickerController *)picker;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,611 @@
|
||||||
|
//
|
||||||
|
// GMGridViewController.m
|
||||||
|
// GMPhotoPicker
|
||||||
|
//
|
||||||
|
// Created by Guillermo Muntaner Perelló on 19/09/14.
|
||||||
|
// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GMGridViewController.h"
|
||||||
|
#import "GMImagePickerController.h"
|
||||||
|
#import "GMAlbumsViewController.h"
|
||||||
|
#import "GMGridViewCell.h"
|
||||||
|
|
||||||
|
#include <Photos/Photos.h>
|
||||||
|
|
||||||
|
|
||||||
|
//Helper methods
|
||||||
|
@implementation NSIndexSet (Convenience)
|
||||||
|
- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
|
||||||
|
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
|
||||||
|
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||||
|
[indexPaths addObject:[NSIndexPath indexPathForItem:(NSInteger)idx inSection:(NSInteger)section]];
|
||||||
|
}];
|
||||||
|
return indexPaths;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation UICollectionView (Convenience)
|
||||||
|
- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
|
||||||
|
NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
|
||||||
|
if (allLayoutAttributes.count == 0) { return nil; }
|
||||||
|
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
|
||||||
|
for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
|
||||||
|
NSIndexPath *indexPath = layoutAttributes.indexPath;
|
||||||
|
[indexPaths addObject:indexPath];
|
||||||
|
}
|
||||||
|
return indexPaths;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMImagePickerController ()
|
||||||
|
|
||||||
|
- (void)finishPickingAssets:(id)sender;
|
||||||
|
- (void)dismiss:(id)sender;
|
||||||
|
- (NSString *)toolbarTitle;
|
||||||
|
- (UIView *)noAssetsView;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface GMGridViewController () <PHPhotoLibraryChangeObserver>
|
||||||
|
|
||||||
|
@property (nonatomic, weak) GMImagePickerController *picker;
|
||||||
|
@property (strong,nonatomic) PHCachingImageManager *imageManager;
|
||||||
|
@property (assign, nonatomic) CGRect previousPreheatRect;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
static CGSize AssetGridThumbnailSize;
|
||||||
|
NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
|
||||||
|
|
||||||
|
@implementation GMGridViewController
|
||||||
|
{
|
||||||
|
CGFloat screenWidth;
|
||||||
|
CGFloat screenHeight;
|
||||||
|
UICollectionViewFlowLayout *portraitLayout;
|
||||||
|
UICollectionViewFlowLayout *landscapeLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(id)initWithPicker:(GMImagePickerController *)picker
|
||||||
|
{
|
||||||
|
//Custom init. The picker contains custom information to create the FlowLayout
|
||||||
|
self.picker = picker;
|
||||||
|
|
||||||
|
//Ipad popover is not affected by rotation!
|
||||||
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||||
|
{
|
||||||
|
screenWidth = CGRectGetWidth(picker.view.bounds);
|
||||||
|
screenHeight = CGRectGetHeight(picker.view.bounds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
|
||||||
|
{
|
||||||
|
screenHeight = CGRectGetWidth(picker.view.bounds);
|
||||||
|
screenWidth = CGRectGetHeight(picker.view.bounds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
screenWidth = CGRectGetWidth(picker.view.bounds);
|
||||||
|
screenHeight = CGRectGetHeight(picker.view.bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
|
||||||
|
if (self = [super initWithCollectionViewLayout:layout])
|
||||||
|
{
|
||||||
|
//Compute the thumbnail pixel size:
|
||||||
|
CGFloat scale = [UIScreen mainScreen].scale;
|
||||||
|
//NSLog(@"This is @%fx scale device", scale);
|
||||||
|
if(scale >= 3)
|
||||||
|
{
|
||||||
|
scale = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
||||||
|
|
||||||
|
self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection;
|
||||||
|
|
||||||
|
[self.collectionView registerClass:GMGridViewCell.class
|
||||||
|
forCellWithReuseIdentifier:GMGridViewCellIdentifier];
|
||||||
|
|
||||||
|
self.preferredContentSize = kPopoverContentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)viewDidLoad
|
||||||
|
{
|
||||||
|
[super viewDidLoad];
|
||||||
|
[self setupViews];
|
||||||
|
|
||||||
|
// Navigation bar customization
|
||||||
|
if (self.picker.customNavigationBarPrompt) {
|
||||||
|
self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.imageManager = [[PHCachingImageManager alloc] init];
|
||||||
|
[self resetCachedAssets];
|
||||||
|
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
|
||||||
|
|
||||||
|
if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
|
||||||
|
{
|
||||||
|
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillAppear:(BOOL)animated
|
||||||
|
{
|
||||||
|
[super viewWillAppear:animated];
|
||||||
|
|
||||||
|
[self setupButtons];
|
||||||
|
[self setupToolbar];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidAppear:(BOOL)animated
|
||||||
|
{
|
||||||
|
[super viewDidAppear:animated];
|
||||||
|
[self updateCachedAssets];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[self resetCachedAssets];
|
||||||
|
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||||
|
return self.picker.pickerStatusBarStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Rotation
|
||||||
|
|
||||||
|
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
|
||||||
|
{
|
||||||
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
|
||||||
|
|
||||||
|
//Update the AssetGridThumbnailSize:
|
||||||
|
CGFloat scale = [UIScreen mainScreen].scale;
|
||||||
|
AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
|
||||||
|
|
||||||
|
[self resetCachedAssets];
|
||||||
|
//This is optional. Reload visible thumbnails:
|
||||||
|
for (GMGridViewCell *cell in [self.collectionView visibleCells]) {
|
||||||
|
NSInteger currentTag = cell.tag;
|
||||||
|
[self.imageManager requestImageForAsset:cell.asset
|
||||||
|
targetSize:AssetGridThumbnailSize
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info)
|
||||||
|
{
|
||||||
|
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
[cell.imageView setImage:result];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.collectionView setCollectionViewLayout:layout animated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Setup
|
||||||
|
|
||||||
|
- (void)setupViews
|
||||||
|
{
|
||||||
|
self.collectionView.backgroundColor = [UIColor clearColor];
|
||||||
|
self.view.backgroundColor = [self.picker pickerBackgroundColor];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupButtons
|
||||||
|
{
|
||||||
|
if (self.picker.allowsMultipleSelection) {
|
||||||
|
NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
|
||||||
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
|
||||||
|
style:UIBarButtonItemStyleDone
|
||||||
|
target:self.picker
|
||||||
|
action:@selector(finishPickingAssets:)];
|
||||||
|
|
||||||
|
self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
|
||||||
|
} else {
|
||||||
|
NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button", @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
|
||||||
|
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
|
||||||
|
style:UIBarButtonItemStyleDone
|
||||||
|
target:self.picker
|
||||||
|
action:@selector(dismiss:)];
|
||||||
|
}
|
||||||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||||||
|
if (self.picker.useCustomFontForNavigationBar) {
|
||||||
|
NSDictionary* barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
|
||||||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
|
||||||
|
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupToolbar
|
||||||
|
{
|
||||||
|
self.toolbarItems = self.picker.toolbarItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Collection View Layout
|
||||||
|
|
||||||
|
- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
|
||||||
|
{
|
||||||
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||||
|
{
|
||||||
|
if(!portraitLayout)
|
||||||
|
{
|
||||||
|
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||||
|
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||||
|
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing);
|
||||||
|
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
||||||
|
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
||||||
|
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
||||||
|
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
||||||
|
portraitLayout.minimumLineSpacing = spaceWidth;
|
||||||
|
}
|
||||||
|
return portraitLayout;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(UIInterfaceOrientationIsLandscape(orientation))
|
||||||
|
{
|
||||||
|
if(!landscapeLayout)
|
||||||
|
{
|
||||||
|
landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||||
|
landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||||
|
int cellTotalUsableWidth = (int)(screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing);
|
||||||
|
landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
|
||||||
|
double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
|
||||||
|
double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
|
||||||
|
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
|
||||||
|
landscapeLayout.minimumLineSpacing = spaceWidth;
|
||||||
|
}
|
||||||
|
return landscapeLayout;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!portraitLayout)
|
||||||
|
{
|
||||||
|
portraitLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||||
|
portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
|
||||||
|
int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1) * self.picker.minimumInteritemSpacing);
|
||||||
|
portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
|
||||||
|
double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
|
||||||
|
double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
|
||||||
|
double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
|
||||||
|
portraitLayout.minimumLineSpacing = spaceWidth;
|
||||||
|
}
|
||||||
|
return portraitLayout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Collection View Data Source
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
|
||||||
|
forIndexPath:indexPath];
|
||||||
|
|
||||||
|
// Increment the cell's tag
|
||||||
|
NSInteger currentTag = cell.tag + 1;
|
||||||
|
cell.tag = currentTag;
|
||||||
|
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
/*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||||
|
{
|
||||||
|
NSLog(@"Image manager: Requesting FIT image for iPad");
|
||||||
|
[self.imageManager requestImageForAsset:asset
|
||||||
|
targetSize:AssetGridThumbnailSize
|
||||||
|
contentMode:PHImageContentModeAspectFit
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
|
||||||
|
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
[cell.imageView setImage:result];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
else*/
|
||||||
|
{
|
||||||
|
//NSLog(@"Image manager: Requesting FILL image for iPhone");
|
||||||
|
[self.imageManager requestImageForAsset:asset
|
||||||
|
targetSize:AssetGridThumbnailSize
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil
|
||||||
|
resultHandler:^(UIImage *result, NSDictionary *info) {
|
||||||
|
|
||||||
|
// Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
|
||||||
|
if (cell.tag == currentTag) {
|
||||||
|
[cell.imageView setImage:result];
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[cell bind:asset];
|
||||||
|
|
||||||
|
cell.shouldShowSelection = self.picker.allowsMultipleSelection;
|
||||||
|
|
||||||
|
// Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)]) {
|
||||||
|
cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
|
||||||
|
} else {
|
||||||
|
cell.enabled = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting `selected` property blocks further deselection. Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
|
||||||
|
if ([self.picker.selectedAssets containsObject:asset]) {
|
||||||
|
cell.selected = YES;
|
||||||
|
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
|
||||||
|
} else {
|
||||||
|
cell.selected = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Collection View Delegate
|
||||||
|
|
||||||
|
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
||||||
|
|
||||||
|
if (!cell.isEnabled) {
|
||||||
|
return NO;
|
||||||
|
} else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)]) {
|
||||||
|
return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
|
||||||
|
} else {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
[self.picker selectAsset:asset];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)]) {
|
||||||
|
[self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) {
|
||||||
|
return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
|
||||||
|
} else {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
[self.picker deselectAsset:asset];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)]) {
|
||||||
|
[self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) {
|
||||||
|
return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
|
||||||
|
} else {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) {
|
||||||
|
[self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
|
||||||
|
if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) {
|
||||||
|
[self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - UICollectionViewDataSource
|
||||||
|
|
||||||
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
NSInteger count = (NSInteger)self.assetsFetchResults.count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - PHPhotoLibraryChangeObserver
|
||||||
|
|
||||||
|
- (void)photoLibraryDidChange:(PHChange *)changeInstance
|
||||||
|
{
|
||||||
|
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
|
||||||
|
// check if there are changes to the assets (insertions, deletions, updates)
|
||||||
|
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
|
||||||
|
if (collectionChanges) {
|
||||||
|
|
||||||
|
// get the new fetch result
|
||||||
|
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
|
||||||
|
|
||||||
|
UICollectionView *collectionView = self.collectionView;
|
||||||
|
|
||||||
|
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
|
||||||
|
// we need to reload all if the incremental diffs are not available
|
||||||
|
[collectionView reloadData];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// if we have incremental diffs, tell the collection view to animate insertions and deletions
|
||||||
|
[collectionView performBatchUpdates:^{
|
||||||
|
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
|
||||||
|
if ([removedIndexes count]) {
|
||||||
|
[collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||||
|
}
|
||||||
|
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
|
||||||
|
if ([insertedIndexes count]) {
|
||||||
|
[collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||||
|
if (self.picker.showCameraButton && self.picker.autoSelectCameraImages) {
|
||||||
|
for (NSIndexPath *path in [insertedIndexes aapl_indexPathsFromIndexesWithSection:0]) {
|
||||||
|
[self collectionView:collectionView didSelectItemAtIndexPath:path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
|
||||||
|
if ([changedIndexes count]) {
|
||||||
|
[collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
|
||||||
|
}
|
||||||
|
} completion:NULL];
|
||||||
|
}
|
||||||
|
|
||||||
|
[self resetCachedAssets];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - UIScrollViewDelegate
|
||||||
|
|
||||||
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||||
|
{
|
||||||
|
[self updateCachedAssets];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Asset Caching
|
||||||
|
|
||||||
|
- (void)resetCachedAssets
|
||||||
|
{
|
||||||
|
[self.imageManager stopCachingImagesForAllAssets];
|
||||||
|
self.previousPreheatRect = CGRectZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateCachedAssets
|
||||||
|
{
|
||||||
|
BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
|
||||||
|
if (!isViewVisible) { return; }
|
||||||
|
|
||||||
|
// The preheat window is twice the height of the visible rect
|
||||||
|
CGRect preheatRect = self.collectionView.bounds;
|
||||||
|
preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
|
||||||
|
|
||||||
|
// If scrolled by a "reasonable" amount...
|
||||||
|
CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
|
||||||
|
if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
|
||||||
|
|
||||||
|
// Compute the assets to start caching and to stop caching.
|
||||||
|
NSMutableArray *addedIndexPaths = [NSMutableArray array];
|
||||||
|
NSMutableArray *removedIndexPaths = [NSMutableArray array];
|
||||||
|
|
||||||
|
[self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
|
||||||
|
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
|
||||||
|
[removedIndexPaths addObjectsFromArray:indexPaths];
|
||||||
|
} addedHandler:^(CGRect addedRect) {
|
||||||
|
NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
|
||||||
|
[addedIndexPaths addObjectsFromArray:indexPaths];
|
||||||
|
}];
|
||||||
|
|
||||||
|
NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
|
||||||
|
NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
|
||||||
|
|
||||||
|
[self.imageManager startCachingImagesForAssets:assetsToStartCaching
|
||||||
|
targetSize:AssetGridThumbnailSize
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil];
|
||||||
|
[self.imageManager stopCachingImagesForAssets:assetsToStopCaching
|
||||||
|
targetSize:AssetGridThumbnailSize
|
||||||
|
contentMode:PHImageContentModeAspectFill
|
||||||
|
options:nil];
|
||||||
|
|
||||||
|
self.previousPreheatRect = preheatRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
|
||||||
|
{
|
||||||
|
if (CGRectIntersectsRect(newRect, oldRect)) {
|
||||||
|
CGFloat oldMaxY = CGRectGetMaxY(oldRect);
|
||||||
|
CGFloat oldMinY = CGRectGetMinY(oldRect);
|
||||||
|
CGFloat newMaxY = CGRectGetMaxY(newRect);
|
||||||
|
CGFloat newMinY = CGRectGetMinY(newRect);
|
||||||
|
if (newMaxY > oldMaxY) {
|
||||||
|
CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
|
||||||
|
addedHandler(rectToAdd);
|
||||||
|
}
|
||||||
|
if (oldMinY > newMinY) {
|
||||||
|
CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
|
||||||
|
addedHandler(rectToAdd);
|
||||||
|
}
|
||||||
|
if (newMaxY < oldMaxY) {
|
||||||
|
CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
|
||||||
|
removedHandler(rectToRemove);
|
||||||
|
}
|
||||||
|
if (oldMinY < newMinY) {
|
||||||
|
CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
|
||||||
|
removedHandler(rectToRemove);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addedHandler(newRect);
|
||||||
|
removedHandler(oldRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
|
||||||
|
{
|
||||||
|
if (indexPaths.count == 0) { return nil; }
|
||||||
|
|
||||||
|
NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
|
||||||
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
|
PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
|
||||||
|
[assets addObject:asset];
|
||||||
|
}
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// GMImagePicker.h
|
||||||
|
// GMImagePicker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/14/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
//! Project version number for GMImagePicker.
|
||||||
|
FOUNDATION_EXPORT double GMImagePickerVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for GMImagePicker.
|
||||||
|
FOUNDATION_EXPORT const unsigned char GMImagePickerVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <GMImagePicker/PublicHeader.h>
|
||||||
|
|
||||||
|
|
||||||
|
#import <GMImagePicker/GMImagePickerController.h>
|
||||||
|
#import <GMImagePicker/GMAlbumsViewCell.h>
|
||||||
|
#import <GMImagePicker/GMAlbumsViewController.h>
|
||||||
|
#import <GMImagePicker/GMGridViewCell.h>
|
||||||
|
#import <GMImagePicker/GMGridViewController.h>
|
|
@ -0,0 +1,332 @@
|
||||||
|
//
|
||||||
|
// 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
|
|
@ -0,0 +1,388 @@
|
||||||
|
//
|
||||||
|
// 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.
After Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 158 B |
Binary file not shown.
After Width: | Height: | Size: 194 B |
|
@ -0,0 +1,22 @@
|
||||||
|
<?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.
|
@ -18,6 +18,10 @@
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
||||||
|
D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; };
|
||||||
|
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
|
||||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||||
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
|
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
|
||||||
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
@ -67,6 +71,9 @@
|
||||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
|
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
|
||||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; };
|
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; };
|
||||||
D627FF81217FE8F400CC0648 /* BehaviorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */; };
|
D627FF81217FE8F400CC0648 /* BehaviorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */; };
|
||||||
|
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B4E21EA695800FE4B39 /* StatusContentType.swift */; };
|
||||||
|
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */; };
|
||||||
|
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; };
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
||||||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
|
||||||
|
@ -110,8 +117,36 @@
|
||||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; };
|
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; };
|
||||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
||||||
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D679C09E215850EF00DA27FE /* XCBActions.swift */; };
|
||||||
|
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */; };
|
||||||
|
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
|
||||||
|
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
|
||||||
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
|
||||||
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */; };
|
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */; };
|
||||||
D67E051521643C77000E0927 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E051421643C77000E0927 /* Tab.swift */; };
|
D67E051521643C77000E0927 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67E051421643C77000E0927 /* Tab.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 */; };
|
||||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */; };
|
||||||
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
D6A5FAFB217B86CE003DB2D9 /* OnboardingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */; };
|
||||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||||
|
@ -150,6 +185,13 @@
|
||||||
remoteGlobalIDString = 04496BC7216252E5001F1B23;
|
remoteGlobalIDString = 04496BC7216252E5001F1B23;
|
||||||
remoteInfo = TTTAttributedLabel;
|
remoteInfo = TTTAttributedLabel;
|
||||||
};
|
};
|
||||||
|
D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = D60A548A21ED515800F1F87C;
|
||||||
|
remoteInfo = GMImagePicker;
|
||||||
|
};
|
||||||
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
@ -194,6 +236,7 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
|
D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */,
|
||||||
04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
|
04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
|
||||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||||
|
@ -216,6 +259,10 @@
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
||||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
|
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
|
||||||
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -266,6 +313,9 @@
|
||||||
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; };
|
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; };
|
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorTableViewController.swift; sourceTree = "<group>"; };
|
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentType.swift; sourceTree = "<group>"; };
|
||||||
|
D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; };
|
||||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||||
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
||||||
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -308,8 +358,36 @@
|
||||||
D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; };
|
D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; };
|
||||||
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
|
||||||
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
|
D679C09E215850EF00DA27FE /* XCBActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActions.swift; sourceTree = "<group>"; };
|
||||||
|
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeAccountDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
|
||||||
|
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
|
||||||
|
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
|
||||||
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceTableViewController.swift; sourceTree = "<group>"; };
|
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D67E051421643C77000E0927 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = "<group>"; };
|
D67E051421643C77000E0927 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.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>"; };
|
||||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeViewController.xib; sourceTree = "<group>"; };
|
||||||
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
D6A5FAFA217B86CE003DB2D9 /* OnboardingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardingViewController.xib; sourceTree = "<group>"; };
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -353,6 +431,13 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D60A548821ED515800F1F87C /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
D61099A82144B0CC00432DC2 /* Frameworks */ = {
|
D61099A82144B0CC00432DC2 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -372,6 +457,7 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */,
|
||||||
04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
|
04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
|
||||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||||
|
@ -407,6 +493,48 @@
|
||||||
path = TTTAttributedLabel;
|
path = TTTAttributedLabel;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D60A548C21ED515800F1F87C /* GMImagePicker */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D60A548D21ED515800F1F87C /* GMImagePicker.h */,
|
||||||
|
D60A548E21ED515800F1F87C /* Info.plist */,
|
||||||
|
D686327A21ED8313008C716E /* Base.lproj */,
|
||||||
|
D686327021ED8312008C716E /* ca.lproj */,
|
||||||
|
D686329221ED8319008C716E /* de.lproj */,
|
||||||
|
D686328C21ED8317008C716E /* en.lproj */,
|
||||||
|
D686326D21ED8312008C716E /* es.lproj */,
|
||||||
|
D686328321ED8316008C716E /* fr.lproj */,
|
||||||
|
D686327E21ED8314008C716E /* GMAlbumsViewCell.h */,
|
||||||
|
D686328121ED8315008C716E /* GMAlbumsViewCell.m */,
|
||||||
|
D686328221ED8316008C716E /* GMAlbumsViewController.h */,
|
||||||
|
D686327821ED8313008C716E /* GMAlbumsViewController.m */,
|
||||||
|
D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */,
|
||||||
|
D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */,
|
||||||
|
D686328B21ED8317008C716E /* GMGridViewCell.h */,
|
||||||
|
D686327321ED8312008C716E /* GMGridViewCell.m */,
|
||||||
|
D686327F21ED8315008C716E /* GMGridViewController.h */,
|
||||||
|
D686329021ED8319008C716E /* GMGridViewController.m */,
|
||||||
|
D686329121ED8319008C716E /* GMImagePickerController.h */,
|
||||||
|
D686328A21ED8317008C716E /* GMImagePickerController.m */,
|
||||||
|
D686328021ED8315008C716E /* GMSelected.png */,
|
||||||
|
D686328921ED8317008C716E /* GMSelected@2x.png */,
|
||||||
|
D686327921ED8313008C716E /* GMVideoIcon.png */,
|
||||||
|
D686327421ED8312008C716E /* GMVideoIcon@2x.png */,
|
||||||
|
D686327521ED8313008C716E /* it.lproj */,
|
||||||
|
D686328621ED8317008C716E /* pt.lproj */,
|
||||||
|
);
|
||||||
|
path = GMImagePicker;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D60C07E221E817560057FAA8 /* Compose Media */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */,
|
||||||
|
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
||||||
|
);
|
||||||
|
path = "Compose Media";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -488,6 +616,7 @@
|
||||||
D61099FA214569F600432DC2 /* Report.swift */,
|
D61099FA214569F600432DC2 /* Report.swift */,
|
||||||
D61099FC21456A1D00432DC2 /* SearchResults.swift */,
|
D61099FC21456A1D00432DC2 /* SearchResults.swift */,
|
||||||
D61099FE21456A4C00432DC2 /* Status.swift */,
|
D61099FE21456A4C00432DC2 /* Status.swift */,
|
||||||
|
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */,
|
||||||
D6109A10214607D500432DC2 /* Timeline.swift */,
|
D6109A10214607D500432DC2 /* Timeline.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
|
@ -596,6 +725,7 @@
|
||||||
D627FF77217E94F200CC0648 /* Drafts */,
|
D627FF77217E94F200CC0648 /* Drafts */,
|
||||||
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
D6A5FAF0217B7E05003DB2D9 /* ComposeViewController.xib */,
|
||||||
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
D66362702136338600C9CBA2 /* ComposeViewController.swift */,
|
||||||
|
D6285B5221EA708700FE4B39 /* StatusFormat.swift */,
|
||||||
);
|
);
|
||||||
path = Compose;
|
path = Compose;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -615,6 +745,7 @@
|
||||||
children = (
|
children = (
|
||||||
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
|
D663626521360DD700C9CBA2 /* Preferences.storyboard */,
|
||||||
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
|
D663626721360E2C00C9CBA2 /* PreferencesTableViewController.swift */,
|
||||||
|
D6285B5021EA6E6E00FE4B39 /* AdvancedTableViewController.swift */,
|
||||||
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */,
|
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */,
|
||||||
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */,
|
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */,
|
||||||
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */,
|
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */,
|
||||||
|
@ -690,6 +821,7 @@
|
||||||
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */,
|
D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */,
|
||||||
D6333B362137838300CE884A /* AttributedString+Trim.swift */,
|
D6333B362137838300CE884A /* AttributedString+Trim.swift */,
|
||||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */,
|
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */,
|
||||||
|
D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -707,15 +839,98 @@
|
||||||
path = XCallbackURL;
|
path = XCallbackURL;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D67C57A721E2649B00C3118B /* Account Detail */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D67C57AC21E265FC00C3118B /* LargeAccountDetailView.swift */,
|
||||||
|
);
|
||||||
|
path = "Account Detail";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D67C57B021E28F9400C3118B /* Compose Status Reply */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */,
|
||||||
|
D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */,
|
||||||
|
);
|
||||||
|
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>";
|
||||||
|
};
|
||||||
D6BED1722126661300F02DA0 /* Views */ = {
|
D6BED1722126661300F02DA0 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
04496BD621625361001F1B23 /* ContentLabel.swift */,
|
04496BD621625361001F1B23 /* ContentLabel.swift */,
|
||||||
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */,
|
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */,
|
||||||
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
||||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */,
|
|
||||||
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||||
|
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||||
|
D67C57B021E28F9400C3118B /* Compose Status Reply */,
|
||||||
|
D60C07E221E817560057FAA8 /* Compose Media */,
|
||||||
D641C78A213DD926004B4513 /* Status */,
|
D641C78A213DD926004B4513 /* Status */,
|
||||||
D641C78B213DD92F004B4513 /* Profile Header */,
|
D641C78B213DD92F004B4513 /* Profile Header */,
|
||||||
D641C78C213DD937004B4513 /* Notifications */,
|
D641C78C213DD937004B4513 /* Notifications */,
|
||||||
|
@ -759,6 +974,7 @@
|
||||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||||
|
D60A548C21ED515800F1F87C /* GMImagePicker */,
|
||||||
D6D4DDCD212518A000E1C4BB /* Products */,
|
D6D4DDCD212518A000E1C4BB /* Products */,
|
||||||
D65A37F221472F300087646E /* Frameworks */,
|
D65A37F221472F300087646E /* Frameworks */,
|
||||||
);
|
);
|
||||||
|
@ -773,6 +989,7 @@
|
||||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
||||||
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
||||||
04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */,
|
04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */,
|
||||||
|
D60A548B21ED515800F1F87C /* GMImagePicker.framework */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -847,6 +1064,19 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D60A548621ED515800F1F87C /* Headers */ = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
D61099A62144B0CC00432DC2 /* Headers */ = {
|
D61099A62144B0CC00432DC2 /* Headers */ = {
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -876,6 +1106,24 @@
|
||||||
productReference = 04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */;
|
productReference = 04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */;
|
||||||
productType = "com.apple.product-type.framework";
|
productType = "com.apple.product-type.framework";
|
||||||
};
|
};
|
||||||
|
D60A548A21ED515800F1F87C /* GMImagePicker */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = D60A549421ED515800F1F87C /* Build configuration list for PBXNativeTarget "GMImagePicker" */;
|
||||||
|
buildPhases = (
|
||||||
|
D60A548621ED515800F1F87C /* Headers */,
|
||||||
|
D60A548721ED515800F1F87C /* Sources */,
|
||||||
|
D60A548821ED515800F1F87C /* Frameworks */,
|
||||||
|
D60A548921ED515800F1F87C /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = GMImagePicker;
|
||||||
|
productName = GMImagePicker;
|
||||||
|
productReference = D60A548B21ED515800F1F87C /* GMImagePicker.framework */;
|
||||||
|
productType = "com.apple.product-type.framework";
|
||||||
|
};
|
||||||
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
|
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
|
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
|
||||||
|
@ -927,6 +1175,7 @@
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||||
04496BCE216252E5001F1B23 /* PBXTargetDependency */,
|
04496BCE216252E5001F1B23 /* PBXTargetDependency */,
|
||||||
|
D60A549121ED515800F1F87C /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Tusker;
|
name = Tusker;
|
||||||
productName = Tusker;
|
productName = Tusker;
|
||||||
|
@ -982,6 +1231,9 @@
|
||||||
04496BC7216252E5001F1B23 = {
|
04496BC7216252E5001F1B23 = {
|
||||||
CreatedOnToolsVersion = 10.1;
|
CreatedOnToolsVersion = 10.1;
|
||||||
};
|
};
|
||||||
|
D60A548A21ED515800F1F87C = {
|
||||||
|
CreatedOnToolsVersion = 10.1;
|
||||||
|
};
|
||||||
D61099AA2144B0CC00432DC2 = {
|
D61099AA2144B0CC00432DC2 = {
|
||||||
CreatedOnToolsVersion = 10.0;
|
CreatedOnToolsVersion = 10.0;
|
||||||
LastSwiftMigration = 1000;
|
LastSwiftMigration = 1000;
|
||||||
|
@ -1010,6 +1262,12 @@
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
|
de,
|
||||||
|
es,
|
||||||
|
it,
|
||||||
|
fr,
|
||||||
|
pt,
|
||||||
|
ca,
|
||||||
);
|
);
|
||||||
mainGroup = D6D4DDC3212518A000E1C4BB;
|
mainGroup = D6D4DDC3212518A000E1C4BB;
|
||||||
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
|
||||||
|
@ -1022,6 +1280,7 @@
|
||||||
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
||||||
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
||||||
04496BC7216252E5001F1B23 /* TTTAttributedLabel */,
|
04496BC7216252E5001F1B23 /* TTTAttributedLabel */,
|
||||||
|
D60A548A21ED515800F1F87C /* GMImagePicker */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
@ -1034,6 +1293,27 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D60A548921ED515800F1F87C /* Resources */ = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
D61099A92144B0CC00432DC2 /* Resources */ = {
|
D61099A92144B0CC00432DC2 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -1064,6 +1344,8 @@
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||||
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
D663626621360DD700C9CBA2 /* Preferences.storyboard in Resources */,
|
||||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||||
|
D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */,
|
||||||
|
D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */,
|
||||||
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
D667E5E12134937B0057A976 /* StatusTableViewCell.xib in Resources */,
|
||||||
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
D6A5FAF1217B7E05003DB2D9 /* ComposeViewController.xib in Resources */,
|
||||||
D621544D21682AD90003D87D /* TabTableViewCell.xib in Resources */,
|
D621544D21682AD90003D87D /* TabTableViewCell.xib in Resources */,
|
||||||
|
@ -1095,6 +1377,18 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D60A548721ED515800F1F87C /* Sources */ = {
|
||||||
|
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;
|
||||||
|
};
|
||||||
D61099A72144B0CC00432DC2 /* Sources */ = {
|
D61099A72144B0CC00432DC2 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -1122,6 +1416,7 @@
|
||||||
D6109A0F21459B6900432DC2 /* Pagination.swift in Sources */,
|
D6109A0F21459B6900432DC2 /* Pagination.swift in Sources */,
|
||||||
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */,
|
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */,
|
||||||
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */,
|
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */,
|
||||||
|
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */,
|
||||||
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */,
|
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */,
|
||||||
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */,
|
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */,
|
||||||
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */,
|
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */,
|
||||||
|
@ -1150,9 +1445,11 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||||
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
|
||||||
|
D6285B5121EA6E6E00FE4B39 /* AdvancedTableViewController.swift in Sources */,
|
||||||
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */,
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||||
|
@ -1166,6 +1463,7 @@
|
||||||
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
|
||||||
D621544B21682AD30003D87D /* TabTableViewCell.swift in Sources */,
|
D621544B21682AD30003D87D /* TabTableViewCell.swift in Sources */,
|
||||||
|
D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
|
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
|
||||||
|
@ -1189,6 +1487,7 @@
|
||||||
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */,
|
D6C693CA2161253F007D6A6D /* SilentActionPermissionsTableViewController.swift in Sources */,
|
||||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */,
|
||||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||||
|
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||||
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
|
D641C777213CAA9E004B4513 /* ActionNotificationTableViewCell.swift in Sources */,
|
||||||
D627FF74217BBC9700CC0648 /* AppRouter.swift in Sources */,
|
D627FF74217BBC9700CC0648 /* AppRouter.swift in Sources */,
|
||||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||||
|
@ -1196,6 +1495,7 @@
|
||||||
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
|
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
|
||||||
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
|
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||||
|
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
|
||||||
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
|
||||||
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */,
|
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */,
|
||||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
|
||||||
|
@ -1242,6 +1542,11 @@
|
||||||
target = 04496BC7216252E5001F1B23 /* TTTAttributedLabel */;
|
target = 04496BC7216252E5001F1B23 /* TTTAttributedLabel */;
|
||||||
targetProxy = 04496BCD216252E5001F1B23 /* PBXContainerItemProxy */;
|
targetProxy = 04496BCD216252E5001F1B23 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
D60A549121ED515800F1F87C /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = D60A548A21ED515800F1F87C /* GMImagePicker */;
|
||||||
|
targetProxy = D60A549021ED515800F1F87C /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||||
|
@ -1270,6 +1575,70 @@
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
D686326E21ED8312008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686326F21ED8312008C716E /* es */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686327121ED8312008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686327221ED8312008C716E /* ca */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686327621ED8313008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686327721ED8313008C716E /* it */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686327B21ED8313008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686327C21ED8313008C716E /* Base */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686328421ED8316008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686328521ED8316008C716E /* fr */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686328721ED8317008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686328821ED8317008C716E /* pt */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686328D21ED8317008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686328E21ED8317008C716E /* en */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D686329321ED8319008C716E /* GMImagePicker.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
D686329421ED8319008C716E /* de */,
|
||||||
|
);
|
||||||
|
name = GMImagePicker.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
|
D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1337,6 +1706,62 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
D60A549521ED515800F1F87C /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
|
DEVELOPMENT_TEAM = HGYVAQA9FW;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = GMImagePicker/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GMImagePicker;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
D60A549621ED515800F1F87C /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
|
DEVELOPMENT_TEAM = HGYVAQA9FW;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = GMImagePicker/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GMImagePicker;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
VERSION_INFO_PREFIX = "";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
D61099C32144B0CC00432DC2 /* Debug */ = {
|
D61099C32144B0CC00432DC2 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
@ -1690,6 +2115,15 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
D60A549421ED515800F1F87C /* Build configuration list for PBXNativeTarget "GMImagePicker" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
D60A549521ED515800F1F87C /* Debug */,
|
||||||
|
D60A549621ED515800F1F87C /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
|
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// Array+Uniques.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/6/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Array where Element: Hashable {
|
||||||
|
func uniques() -> [Element] {
|
||||||
|
var buffer = [Element]()
|
||||||
|
var added = Set<Element>()
|
||||||
|
for elem in self {
|
||||||
|
if !added.contains(elem) {
|
||||||
|
buffer.append(elem)
|
||||||
|
added.insert(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ class Preferences: Codable {
|
||||||
|
|
||||||
// MARK: - Advanced
|
// MARK: - Advanced
|
||||||
var silentActions: [String: Permission] = [:]
|
var silentActions: [String: Permission] = [:]
|
||||||
|
var statusContentType: StatusContentType = .plain
|
||||||
|
|
||||||
// MARK: - Utility Methods
|
// MARK: - Utility Methods
|
||||||
func tabIndex(_ tab: Tab) -> Int {
|
func tabIndex(_ tab: Tab) -> Int {
|
||||||
|
|
|
@ -9,67 +9,86 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Intents
|
import Intents
|
||||||
|
import Photos
|
||||||
|
import GMImagePicker
|
||||||
|
import MobileCoreServices
|
||||||
|
|
||||||
class ComposeViewController: UIViewController {
|
class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
let router: AppRouter
|
let router: AppRouter
|
||||||
|
|
||||||
@IBOutlet weak var scrollView: UIScrollView!
|
|
||||||
@IBOutlet weak var inReplyToContainerView: UIView!
|
|
||||||
@IBOutlet weak var inReplyToAvatarImageView: UIImageView!
|
|
||||||
@IBOutlet weak var inReplyToDisplayNameLabel: UILabel!
|
|
||||||
@IBOutlet weak var inReplyToUsernameLabel: UILabel!
|
|
||||||
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
|
|
||||||
@IBOutlet weak var inReplyToLabel: UILabel!
|
|
||||||
@IBOutlet weak var statusTextView: UITextView!
|
|
||||||
@IBOutlet weak var placeholderLabel: UILabel!
|
|
||||||
@IBOutlet weak var charactersRemainingLabel: UILabel!
|
|
||||||
@IBOutlet weak var visibilityButton: UIButton!
|
|
||||||
@IBOutlet weak var postButton: UIButton!
|
|
||||||
@IBOutlet weak var contentWarningTextField: UITextField!
|
|
||||||
@IBOutlet weak var mediaStackView: UIStackView!
|
|
||||||
@IBOutlet weak var paddingView: UIView!
|
|
||||||
@IBOutlet weak var progressView: SteppedProgressView!
|
|
||||||
|
|
||||||
var scrolled = false
|
|
||||||
|
|
||||||
var inReplyToID: String?
|
var inReplyToID: String?
|
||||||
|
var accountsToMention: [String]
|
||||||
// TODO: cleanup this
|
|
||||||
var mentioningAcct: String?
|
|
||||||
var text: String?
|
|
||||||
var initialText: String?
|
var initialText: String?
|
||||||
|
var contentWarningEnabled = false {
|
||||||
|
didSet {
|
||||||
|
contentWarningStateChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var visibility: Status.Visibility! {
|
||||||
|
didSet {
|
||||||
|
visibilityChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var selectedAssets: [PHAsset] = [] {
|
||||||
|
didSet {
|
||||||
|
updateAttachmentViews()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var draft: DraftsManager.Draft?
|
var currentDraft: DraftsManager.Draft?
|
||||||
|
|
||||||
// Weak so that if a new session is initiated (i.e. XCBManager.currentSession is changed) while the current one is in progress, this one will be released
|
// Weak so that if a new session is initiated (i.e. XCBManager.currentSession is changed) while the current one is in progress, this one will be released
|
||||||
weak var xcbSession: XCBSession?
|
weak var xcbSession: XCBSession?
|
||||||
|
var postedStatus: Status?
|
||||||
|
|
||||||
var contentWarning = false {
|
weak var postBarButtonItem: UIBarButtonItem!
|
||||||
didSet {
|
var visibilityBarButtonItem: UIBarButtonItem!
|
||||||
contentWarningTextField.isHidden = !contentWarning
|
var contentWarningBarButtonItem: UIBarButtonItem!
|
||||||
}
|
|
||||||
}
|
|
||||||
var visibility = Preferences.shared.defaultPostVisibility {
|
|
||||||
didSet {
|
|
||||||
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var status: Status?
|
@IBOutlet weak var scrollView: UIScrollView!
|
||||||
|
@IBOutlet weak var contentView: UIView!
|
||||||
|
@IBOutlet weak var stackView: UIStackView!
|
||||||
|
|
||||||
|
var replyView: ComposeStatusReplyView?
|
||||||
|
var replyAvatarImageViewTopConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
|
@IBOutlet weak var selfDetailView: LargeAccountDetailView!
|
||||||
|
|
||||||
|
@IBOutlet weak var charactersRemainingLabel: UILabel!
|
||||||
|
@IBOutlet weak var statusTextView: UITextView!
|
||||||
|
@IBOutlet weak var placeholderLabel: UILabel!
|
||||||
|
|
||||||
|
@IBOutlet weak var contentWarningContainerView: UIView!
|
||||||
|
@IBOutlet weak var contentWarningTextField: UITextField!
|
||||||
|
|
||||||
|
@IBOutlet weak var attachmentsStackView: UIStackView!
|
||||||
|
@IBOutlet weak var addAttachmentButton: UIButton!
|
||||||
|
|
||||||
|
@IBOutlet weak var postProgressView: SteppedProgressView!
|
||||||
|
|
||||||
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, router: AppRouter) {
|
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, router: AppRouter) {
|
||||||
self.inReplyToID = inReplyToID
|
|
||||||
self.mentioningAcct = mentioningAcct
|
|
||||||
self.text = text
|
|
||||||
self.router = router
|
self.router = router
|
||||||
|
|
||||||
|
self.inReplyToID = inReplyToID
|
||||||
|
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||||
|
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
||||||
|
} else if let mentioningAcct = mentioningAcct {
|
||||||
|
accountsToMention = [mentioningAcct]
|
||||||
|
} else {
|
||||||
|
accountsToMention = []
|
||||||
|
}
|
||||||
|
if let ownAccount = MastodonController.account {
|
||||||
|
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
|
||||||
|
}
|
||||||
|
accountsToMention = accountsToMention.uniques()
|
||||||
|
|
||||||
super.init(nibName: "ComposeViewController", bundle: nil)
|
super.init(nibName: "ComposeViewController", bundle: nil)
|
||||||
|
|
||||||
title = "Compose"
|
title = "Compose"
|
||||||
|
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed))
|
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsPressed))
|
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .done, target: self, action: #selector(postButtonPressed))
|
||||||
|
postBarButtonItem = navigationItem.rightBarButtonItem
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
@ -79,97 +98,141 @@ class ComposeViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
statusTextView.layer.cornerRadius = 5
|
scrollView.delegate = self
|
||||||
statusTextView.layer.masksToBounds = true
|
|
||||||
statusTextView.delegate = self
|
statusTextView.delegate = self
|
||||||
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
statusTextView.becomeFirstResponder()
|
||||||
contentWarningTextField.delegate = self
|
|
||||||
|
|
||||||
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
|
let toolbar = UIToolbar()
|
||||||
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
contentWarningBarButtonItem = UIBarButtonItem(title: "CW", style: .plain, target: self, action: #selector(contentWarningButtonPressed))
|
||||||
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(keyboardDoneButtonPressed))
|
visibilityBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(visbilityButtonPressed))
|
||||||
toolbar.setItems([flexSpace, done], animated: false)
|
toolbar.items = [
|
||||||
toolbar.sizeToFit()
|
contentWarningBarButtonItem,
|
||||||
|
visibilityBarButtonItem,
|
||||||
|
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||||
|
] + createFormattingButtons() + [
|
||||||
|
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
|
||||||
|
UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsButtonPressed))
|
||||||
|
]
|
||||||
|
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||||
statusTextView.inputAccessoryView = toolbar
|
statusTextView.inputAccessoryView = toolbar
|
||||||
|
contentWarningTextField.inputAccessoryView = toolbar
|
||||||
|
|
||||||
if let inReplyToID = inReplyToID,
|
statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined()
|
||||||
let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
initialText = statusTextView.text
|
||||||
inReplyToDisplayNameLabel.text = inReplyTo.account.realDisplayName
|
|
||||||
inReplyToUsernameLabel.text = "@\(inReplyTo.account.username)"
|
MastodonController.getOwnAccount { (account) in
|
||||||
inReplyToContentLabel.statusID = inReplyToID
|
DispatchQueue.main.async {
|
||||||
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
self.selfDetailView.update(account: account)
|
||||||
inReplyToAvatarImageView.layer.masksToBounds = true
|
|
||||||
inReplyToAvatarImageView.image = nil
|
|
||||||
ImageCache.avatars.get(inReplyTo.account.avatar) { (data) in
|
|
||||||
guard let data = data else { return }
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.inReplyToAvatarImageView.image = UIImage(data: data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
}
|
||||||
if inReplyTo.account != MastodonController.account {
|
|
||||||
statusTextView.text = "@\(inReplyTo.account.acct) "
|
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
|
||||||
}
|
|
||||||
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.account.id }).map({ "@\($0.acct) " }).joined()
|
|
||||||
contentWarning = inReplyTo.sensitive
|
|
||||||
contentWarningTextField.text = inReplyTo.spoilerText
|
|
||||||
visibility = inReplyTo.visibility
|
visibility = inReplyTo.visibility
|
||||||
} else {
|
|
||||||
inReplyToLabel.isHidden = true
|
|
||||||
inReplyToContainerView.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mentioningAcct = mentioningAcct {
|
let replyView = ComposeStatusReplyView.create()
|
||||||
statusTextView.text += "@\(mentioningAcct) "
|
replyView.updateUI(for: inReplyTo)
|
||||||
}
|
stackView.insertArrangedSubview(replyView, at: 0)
|
||||||
if let text = text {
|
self.replyView = replyView
|
||||||
statusTextView.text += text
|
|
||||||
}
|
|
||||||
|
|
||||||
initialText = statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
|
||||||
|
replyAvatarImageViewTopConstraint!.isActive = true
|
||||||
|
|
||||||
|
let replyLabelContainer = UIView()
|
||||||
|
replyLabelContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
let replyLabel = UILabel()
|
||||||
|
replyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
replyLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||||
|
replyLabel.textColor = .darkGray
|
||||||
|
replyLabelContainer.addSubview(replyLabel)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
replyLabel.leadingAnchor.constraint(equalTo: replyLabelContainer.leadingAnchor, constant: 8),
|
||||||
|
replyLabel.trailingAnchor.constraint(equalTo: replyLabelContainer.trailingAnchor, constant: -8),
|
||||||
|
replyLabel.topAnchor.constraint(equalTo: replyLabelContainer.topAnchor),
|
||||||
|
replyLabel.bottomAnchor.constraint(equalTo: replyLabelContainer.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
stackView.insertArrangedSubview(replyLabelContainer, at: 1)
|
||||||
|
}
|
||||||
|
|
||||||
updateCharactersRemaining()
|
updateCharactersRemaining()
|
||||||
updatePlaceholder()
|
updatePlaceholder()
|
||||||
|
|
||||||
progressView.progress = 0
|
contentWarningEnabled = false
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
|
||||||
|
|
||||||
if let mentioningAcct = mentioningAcct {
|
if inReplyToID == nil {
|
||||||
let req = MastodonController.client.searchForAccount(query: mentioningAcct)
|
visibility = Preferences.shared.defaultPostVisibility
|
||||||
MastodonController.client.run(req) { [weak self] (response) in
|
|
||||||
if case let .success(accounts, _) = response {
|
|
||||||
self?.userActivity = UserActivityManager.newPostActivity(mentioning: accounts.first)
|
|
||||||
} else {
|
|
||||||
self?.userActivity = UserActivityManager.newPostActivity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.userActivity = UserActivityManager.newPostActivity()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||||
|
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
if inReplyToID != nil && !scrolled {
|
super.viewDidLayoutSubviews()
|
||||||
scrollView.contentOffset = CGPoint(x: 0, y: inReplyToContainerView.bounds.height - 44)
|
|
||||||
scrolled = true
|
// if inReplyToID != nil {
|
||||||
|
// scrollView.contentOffset = CGPoint(x: 0, y: stackView.arrangedSubviews.first!.frame.height)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFormattingButtons() -> [UIBarButtonItem] {
|
||||||
|
guard Preferences.shared.statusContentType != .plain else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return StatusFormat.allCases.map { (format) in
|
||||||
|
let (str, attributes) = format.title
|
||||||
|
let item = UIBarButtonItem(title: str, style: .plain, target: self, action: #selector(formatButtonPressed(_:)))
|
||||||
|
item.setTitleTextAttributes(attributes, for: .normal)
|
||||||
|
item.setTitleTextAttributes(attributes, for: .highlighted)
|
||||||
|
item.tag = StatusFormat.allCases.firstIndex(of: format)!
|
||||||
|
return item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMedia(for image: UIImage) {
|
@objc func adjustForKeyboard(notification: NSNotification) {
|
||||||
let mediaView = ComposeMediaView(image: image)
|
let userInfo = notification.userInfo!
|
||||||
mediaView.delegate = self
|
|
||||||
mediaStackView.addArrangedSubview(mediaView)
|
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
|
||||||
|
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
|
||||||
|
|
||||||
|
if notification.name == UIResponder.keyboardWillHideNotification {
|
||||||
|
scrollView.contentInset = .zero
|
||||||
|
} else {
|
||||||
|
// let accessoryFrame = view.convert(statusTextView.inputAccessoryView!.frame, from: view.window)
|
||||||
|
let offset = keyboardViewEndFrame.height// + accessoryFrame.height
|
||||||
|
// TODO: radar for incorrect keyboard end frame height (either converted or screen)
|
||||||
|
// the value returned is somewhere between the height of the keyboard and the height of the keyboard + accessory
|
||||||
|
// actually maybe not??
|
||||||
|
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: offset, right: 0)
|
||||||
|
}
|
||||||
|
scrollView.scrollIndicatorInsets = scrollView.contentInset
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCharactersRemaining() {
|
func updateCharactersRemaining() {
|
||||||
|
// TODO: include CW char count
|
||||||
let count = CharacterCounter.count(text: statusTextView.text)
|
let count = CharacterCounter.count(text: statusTextView.text)
|
||||||
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count
|
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
|
||||||
|
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount
|
||||||
if remaining < 0 {
|
if remaining < 0 {
|
||||||
charactersRemainingLabel.textColor = .red
|
charactersRemainingLabel.textColor = .red
|
||||||
postButton.isEnabled = false
|
postBarButtonItem.isEnabled = false
|
||||||
} else {
|
} else {
|
||||||
charactersRemainingLabel.textColor = .darkGray
|
charactersRemainingLabel.textColor = .darkGray
|
||||||
postButton.isEnabled = true
|
postBarButtonItem.isEnabled = true
|
||||||
}
|
}
|
||||||
charactersRemainingLabel.text = remaining.description
|
charactersRemainingLabel.text = remaining.description
|
||||||
}
|
}
|
||||||
|
@ -178,147 +241,65 @@ class ComposeViewController: UIViewController {
|
||||||
placeholderLabel.isHidden = !statusTextView.text.isEmpty
|
placeholderLabel.isHidden = !statusTextView.text.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Navigation
|
func updateAddAttachmentButton() {
|
||||||
|
addAttachmentButton.isEnabled = selectedAssets.count < 5 // 4 attachments + 1 button
|
||||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
|
||||||
statusTextView.resignFirstResponder()
|
|
||||||
contentWarningTextField.resignFirstResponder()
|
|
||||||
super.dismiss(animated: flag, completion: completion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interaction
|
func updateAttachmentViews() {
|
||||||
|
for view in attachmentsStackView.arrangedSubviews {
|
||||||
@IBAction func visibilityPressed(_ sender: Any) {
|
if view is ComposeMediaView {
|
||||||
let alertController = UIAlertController(currentVisibility: self.visibility) { (visibility) in
|
view.removeFromSuperview()
|
||||||
guard let visibility = visibility else { return }
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
self.visibility = visibility
|
|
||||||
self.visibilityButton.layoutIfNeeded()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
present(alertController, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func contentWarningPressed(_ sender: Any) {
|
for asset in selectedAssets {
|
||||||
contentWarning = !contentWarning
|
let mediaView = ComposeMediaView.create()
|
||||||
}
|
mediaView.delegate = self
|
||||||
|
mediaView.update(asset: asset)
|
||||||
@IBAction func mediaPressed(_ sender: Any) {
|
attachmentsStackView.insertArrangedSubview(mediaView, at: attachmentsStackView.arrangedSubviews.count - 1)
|
||||||
let imagePicker = UIImagePickerController()
|
updateAddAttachmentButton()
|
||||||
imagePicker.delegate = self
|
|
||||||
|
|
||||||
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
|
|
||||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
|
||||||
alertController.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
|
|
||||||
imagePicker.sourceType = .camera
|
|
||||||
self.present(imagePicker, animated: true)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
|
|
||||||
alertController.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { __ in
|
|
||||||
imagePicker.sourceType = .photoLibrary
|
|
||||||
self.present(imagePicker, animated: true)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
||||||
present(alertController, animated: true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func postPressed(_ sender: Any) {
|
func contentWarningStateChanged() {
|
||||||
guard let text = statusTextView.text,
|
contentWarningContainerView.isHidden = !contentWarningEnabled
|
||||||
!text.isEmpty else { return }
|
contentWarningBarButtonItem.style = contentWarningEnabled ? .done : .plain
|
||||||
|
if contentWarningEnabled {
|
||||||
postButton.isEnabled = false
|
contentWarningTextField.becomeFirstResponder()
|
||||||
|
|
||||||
let contentWarning: String?
|
|
||||||
if self.contentWarning,
|
|
||||||
let text = contentWarningTextField.text,
|
|
||||||
!text.isEmpty {
|
|
||||||
contentWarning = text
|
|
||||||
} else {
|
} else {
|
||||||
contentWarning = nil
|
statusTextView.becomeFirstResponder()
|
||||||
}
|
|
||||||
let sensitive = contentWarning != nil
|
|
||||||
|
|
||||||
let visibility = self.visibility
|
|
||||||
|
|
||||||
var attachments: [Attachment?] = []
|
|
||||||
let group = DispatchGroup()
|
|
||||||
for view in mediaStackView.arrangedSubviews {
|
|
||||||
guard let mediaView = view as? ComposeMediaView,
|
|
||||||
let image = mediaView.image,
|
|
||||||
let data = image.pngData() else { continue }
|
|
||||||
let index = attachments.count
|
|
||||||
attachments.append(nil)
|
|
||||||
progressView.steps += 1
|
|
||||||
group.enter()
|
|
||||||
let request = MastodonController.client.upload(attachment: FormAttachment(pngData: data), description: mediaView.mediaDescription)
|
|
||||||
MastodonController.client.run(request) { response in
|
|
||||||
guard case let .success(attachment, _) = response else { fatalError() }
|
|
||||||
attachments[index] = attachment
|
|
||||||
self.progressView.step()
|
|
||||||
group.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressView.steps = 2 + attachments.count
|
|
||||||
progressView.currentStep = 1
|
|
||||||
|
|
||||||
group.notify(queue: .main) {
|
|
||||||
let attachments = attachments.compactMap { $0 }
|
|
||||||
|
|
||||||
let request = MastodonController.client.createStatus(text: text,
|
|
||||||
inReplyTo: self.inReplyToID,
|
|
||||||
media: attachments,
|
|
||||||
sensitive: sensitive,
|
|
||||||
spoilerText: contentWarning,
|
|
||||||
visibility: visibility)
|
|
||||||
MastodonController.client.run(request) { response in
|
|
||||||
guard case let .success(status, _) = response else { fatalError() }
|
|
||||||
self.status = status
|
|
||||||
MastodonCache.add(status: status)
|
|
||||||
|
|
||||||
if let draft = self.draft {
|
|
||||||
DraftsManager.shared.remove(draft)
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.progressView.step()
|
|
||||||
self.dismiss(animated: true)
|
|
||||||
// TODO: reorganize routing/navigation
|
|
||||||
(((self.presentingViewController as! MainTabBarViewController).selectedViewController as! UINavigationController).topViewController as! TuskerNavigationDelegate).selected(status: status.id)
|
|
||||||
|
|
||||||
self.xcbSession?.complete(with: .success, additionalData: [
|
|
||||||
"statusURL": status.url?.absoluteString,
|
|
||||||
"statusURI": status.uri
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func imagePressed(_ gesture: UITapGestureRecognizer) {
|
func visibilityChanged() {
|
||||||
gesture.view!.superview!.removeFromSuperview()
|
// TODO: update visiblity button image
|
||||||
}
|
|
||||||
|
|
||||||
@objc func keyboardDoneButtonPressed() {
|
|
||||||
statusTextView.endEditing(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveDraft() {
|
func saveDraft() {
|
||||||
if let draft = draft {
|
// TODO: save attachmenst to drafts
|
||||||
draft.update(text: statusTextView.text)
|
// TODO: save CW to draft
|
||||||
|
if let currentDraft = currentDraft {
|
||||||
|
currentDraft.update(text: statusTextView.text)
|
||||||
} else {
|
} else {
|
||||||
DraftsManager.shared.create(text: statusTextView.text)
|
DraftsManager.shared.create(text: statusTextView.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func close() {
|
@objc func close() {
|
||||||
dismiss(animated: true)
|
dismiss(animated: true)
|
||||||
xcbSession?.complete(with: .cancel)
|
xcbSession?.complete(with: .cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func cancelPressed() {
|
// MARK: - Navigation
|
||||||
|
|
||||||
|
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||||
|
statusTextView.resignFirstResponder()
|
||||||
|
super.dismiss(animated: flag, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Interaction
|
||||||
|
|
||||||
|
@objc func cancelButtonPressed() {
|
||||||
guard statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) != initialText else {
|
guard statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) != initialText else {
|
||||||
close()
|
close()
|
||||||
return
|
return
|
||||||
|
@ -336,28 +317,177 @@ class ComposeViewController: UIViewController {
|
||||||
self.close()
|
self.close()
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Delete draft", style: .destructive, handler: { (_) in
|
alert.addAction(UIAlertAction(title: "Delete draft", style: .destructive, handler: { (_) in
|
||||||
if let draft = self.draft {
|
if let currentDraft = self.currentDraft {
|
||||||
DraftsManager.shared.remove(draft)
|
DraftsManager.shared.remove(currentDraft)
|
||||||
}
|
}
|
||||||
self.close()
|
self.close()
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||||
}))
|
|
||||||
router.present(alert, animated: true)
|
router.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func draftsPressed() {
|
@objc func contentWarningButtonPressed() {
|
||||||
let drafts = router.drafts()
|
contentWarningEnabled = !contentWarningEnabled
|
||||||
drafts.delegate = self
|
}
|
||||||
router.present(UINavigationController(rootViewController: drafts), animated: true)
|
|
||||||
|
@objc func contentWarningTextFieldDidChange() {
|
||||||
|
updateCharactersRemaining()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func visbilityButtonPressed() {
|
||||||
|
let alertController = UIAlertController(currentVisibility: self.visibility) { (visibility) in
|
||||||
|
guard let visibility = visibility else { return }
|
||||||
|
self.visibility = visibility
|
||||||
|
}
|
||||||
|
present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func formatButtonPressed(_ button: UIBarButtonItem) {
|
||||||
|
guard statusTextView.isFirstResponder else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = StatusFormat.allCases[button.tag]
|
||||||
|
guard let insertionResult = format.insertionResult else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentSelectedRange = statusTextView.selectedRange
|
||||||
|
if currentSelectedRange.length == 0 {
|
||||||
|
statusTextView.insertText(insertionResult.prefix + insertionResult.suffix)
|
||||||
|
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.insertionPoint, length: 0)
|
||||||
|
} else {
|
||||||
|
let start = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.lowerBound)
|
||||||
|
let end = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.upperBound)
|
||||||
|
let selectedText = statusTextView.text[start..<end]
|
||||||
|
statusTextView.insertText(String(insertionResult.prefix + selectedText + insertionResult.suffix))
|
||||||
|
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.prefix.count, length: currentSelectedRange.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func draftsButtonPressed() {
|
||||||
|
let draftsVC = router.drafts()
|
||||||
|
draftsVC.delegate = self
|
||||||
|
router.present(UINavigationController(rootViewController: draftsVC), animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func postButtonPressed() {
|
||||||
|
guard let text = statusTextView.text,
|
||||||
|
!text.isEmpty else { return }
|
||||||
|
|
||||||
|
// disable post button while sending post request
|
||||||
|
postBarButtonItem.isEnabled = false
|
||||||
|
|
||||||
|
let contentWarning: String?
|
||||||
|
if contentWarningEnabled, let cwText = contentWarningTextField.text, !cwText.isEmpty {
|
||||||
|
contentWarning = cwText
|
||||||
|
} else {
|
||||||
|
contentWarning = nil
|
||||||
|
}
|
||||||
|
let sensitive = contentWarning != nil
|
||||||
|
let visibility = self.visibility!
|
||||||
|
|
||||||
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
var attachments: [Attachment?] = []
|
||||||
|
for asset in selectedAssets {
|
||||||
|
let index = attachments.count
|
||||||
|
attachments.append(nil)
|
||||||
|
|
||||||
|
let mediaView = attachmentsStackView.arrangedSubviews[index] as! ComposeMediaView
|
||||||
|
let description = mediaView.descriptionTextView.text
|
||||||
|
|
||||||
|
group.enter()
|
||||||
|
|
||||||
|
let options = PHImageRequestOptions()
|
||||||
|
options.version = .current
|
||||||
|
options.deliveryMode = .highQualityFormat
|
||||||
|
options.resizeMode = .none
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
|
PHImageManager.default().requestImageData(for: asset, options: options) { (data, dataUTI, orientation, info) in
|
||||||
|
guard let data = data, let dataUTI = dataUTI else { fatalError() }
|
||||||
|
|
||||||
|
let mimeType = UTTypeCopyPreferredTagWithClass(dataUTI as CFString, kUTTagClassMIMEType)!.takeRetainedValue() as String
|
||||||
|
|
||||||
|
self.postProgressView.step()
|
||||||
|
|
||||||
|
let request = MastodonController.client.upload(attachment: FormAttachment(mimeType: mimeType, data: data, fileName: "file"), description: description)
|
||||||
|
MastodonController.client.run(request) { (response) in
|
||||||
|
guard case let .success(attachment, _) = response else { fatalError() }
|
||||||
|
|
||||||
|
attachments[index] = attachment
|
||||||
|
|
||||||
|
self.postProgressView.step()
|
||||||
|
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postProgressView.steps = 2 + (attachments.count * 2) // 2 steps (request data, then upload) for each attachment
|
||||||
|
postProgressView.currentStep = 1
|
||||||
|
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
let attachments = attachments.compactMap { $0 }
|
||||||
|
|
||||||
|
let request = MastodonController.client.createStatus(text: text,
|
||||||
|
contentType: Preferences.shared.statusContentType,
|
||||||
|
inReplyTo: self.inReplyToID,
|
||||||
|
media: attachments,
|
||||||
|
sensitive: sensitive,
|
||||||
|
spoilerText: contentWarning,
|
||||||
|
visibility: visibility,
|
||||||
|
language: nil)
|
||||||
|
MastodonController.client.run(request) { (response) in
|
||||||
|
guard case let .success(status, _) = response else { fatalError() }
|
||||||
|
self.postedStatus = status
|
||||||
|
MastodonCache.add(status: status)
|
||||||
|
|
||||||
|
if let draft = self.currentDraft {
|
||||||
|
DraftsManager.shared.remove(draft)
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.postProgressView.step()
|
||||||
|
self.dismiss(animated: true)
|
||||||
|
|
||||||
|
let conversationVC = self.router.conversation(for: status.id)
|
||||||
|
self.router.push(conversationVC, animated: true)
|
||||||
|
|
||||||
|
self.xcbSession?.complete(with: .success, additionalData: [
|
||||||
|
"statusURL": status.url?.absoluteString,
|
||||||
|
"statusURI": status.uri
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeViewController: UITextFieldDelegate {
|
extension ComposeViewController: UIScrollViewDelegate {
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
textField.endEditing(false)
|
guard let replyView = replyView else { return }
|
||||||
return true
|
|
||||||
|
var constant: CGFloat = 8
|
||||||
|
|
||||||
|
if scrollView.contentOffset.y < 0 {
|
||||||
|
constant -= scrollView.contentOffset.y
|
||||||
|
replyAvatarImageViewTopConstraint?.constant = 8 - scrollView.contentOffset.y
|
||||||
|
} else if scrollView.contentOffset.y > replyView.frame.height - replyView.avatarImageView.frame.height - 16 {
|
||||||
|
constant += replyView.frame.height - replyView.avatarImageView.frame.height - 16 - scrollView.contentOffset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
replyAvatarImageViewTopConstraint?.constant = constant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,27 +498,23 @@ extension ComposeViewController: UITextViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
extension ComposeViewController: GMImagePickerControllerDelegate {
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
func assetsPickerController(_ picker: GMImagePickerController!, didFinishPickingAssets assets: [Any]!) {
|
||||||
if let selectedImage = info[.originalImage] as? UIImage {
|
let assets = assets as! [PHAsset]
|
||||||
addMedia(for: selectedImage)
|
selectedAssets.append(contentsOf: assets)
|
||||||
dismiss(animated: true)
|
picker.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assetsPickerController(_ picker: GMImagePickerController!, shouldSelect asset: PHAsset!) -> Bool {
|
||||||
|
return selectedAssets.count + picker.selectedAssets.count < 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComposeViewController: ComposeMediaViewDelegate {
|
extension ComposeViewController: ComposeMediaViewDelegate {
|
||||||
func editDescription(for media: ComposeMediaView) {
|
func didRemoveMedia(_ mediaView: ComposeMediaView) {
|
||||||
let alertController = UIAlertController(title: "Media Description", message: nil, preferredStyle: .alert)
|
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
|
||||||
alertController.addTextField { textField in
|
selectedAssets.remove(at: index)
|
||||||
textField.text = media.mediaDescription
|
updateAddAttachmentButton()
|
||||||
}
|
|
||||||
alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { _ in
|
|
||||||
let description = alertController.textFields![0].text
|
|
||||||
media.mediaDescription = description
|
|
||||||
}))
|
|
||||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
||||||
present(alertController, animated: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,8 +523,9 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func draftSelected(_ draft: DraftsManager.Draft) {
|
func draftSelected(_ draft: DraftsManager.Draft) {
|
||||||
self.draft = draft
|
self.currentDraft = draft
|
||||||
statusTextView.text = draft.text
|
statusTextView.text = draft.text
|
||||||
updatePlaceholder()
|
updatePlaceholder()
|
||||||
|
updateCharactersRemaining()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,29 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.23.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.16.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ComposeViewController" customModule="Tusker" customModuleProvider="target">
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ComposeViewController" customModule="Tusker" customModuleProvider="target">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="charactersRemainingLabel" destination="3Ti-cT-JnH" id="hhy-k7-jBN"/>
|
<outlet property="addAttachmentButton" destination="eEV-Yt-Njk" id="o9g-pP-dtd"/>
|
||||||
<outlet property="contentWarningTextField" destination="FA4-UR-Pc9" id="UnZ-6T-Phn"/>
|
<outlet property="attachmentsStackView" destination="P0F-3w-gI1" id="Bi5-EK-N3a"/>
|
||||||
<outlet property="inReplyToAvatarImageView" destination="cOi-cF-kIg" id="bCo-zq-NYI"/>
|
<outlet property="charactersRemainingLabel" destination="PMB-Wa-Ht0" id="PN9-wr-Pzu"/>
|
||||||
<outlet property="inReplyToContainerView" destination="YTO-vO-WZ5" id="gsc-Qh-Roi"/>
|
<outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/>
|
||||||
<outlet property="inReplyToContentLabel" destination="a2k-Ax-jJd" id="wSz-ga-J2W"/>
|
<outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/>
|
||||||
<outlet property="inReplyToDisplayNameLabel" destination="77P-er-bd4" id="GrH-Cu-Sqw"/>
|
<outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/>
|
||||||
<outlet property="inReplyToLabel" destination="lZc-Z2-0vV" id="KSM-Kh-lgR"/>
|
<outlet property="placeholderLabel" destination="EW3-YK-vPC" id="Rsw-Nv-TNz"/>
|
||||||
<outlet property="inReplyToUsernameLabel" destination="ZNM-ey-3j8" id="hMp-DJ-7kB"/>
|
<outlet property="postProgressView" destination="Tq7-6P-hMT" id="amT-F1-JI0"/>
|
||||||
<outlet property="mediaStackView" destination="wHz-h7-939" id="2gr-Vs-ymW"/>
|
<outlet property="scrollView" destination="6Z0-Vy-hMX" id="ya0-2T-QaV"/>
|
||||||
<outlet property="paddingView" destination="YyO-d2-4bx" id="wPp-TI-5Zc"/>
|
<outlet property="selfDetailView" destination="zZ3-Gv-4P5" id="jou-Vl-TQE"/>
|
||||||
<outlet property="placeholderLabel" destination="blO-2d-WiC" id="cG4-Gt-c4T"/>
|
<outlet property="stackView" destination="bOB-hF-O9w" id="lD7-b2-MWl"/>
|
||||||
<outlet property="postButton" destination="i4l-LR-1kp" id="BmN-Zr-OcQ"/>
|
<outlet property="statusTextView" destination="9pn-0T-IHb" id="u7j-KW-zCo"/>
|
||||||
<outlet property="progressView" destination="Ncd-80-ldI" id="ZNz-kk-Rjl"/>
|
|
||||||
<outlet property="scrollView" destination="cp4-lI-aME" id="dqM-XD-7EF"/>
|
|
||||||
<outlet property="statusTextView" destination="5Yb-vo-0yN" id="r6k-fK-U5F"/>
|
|
||||||
<outlet property="view" destination="7XG-Dk-OGm" id="09I-sr-hnP"/>
|
<outlet property="view" destination="7XG-Dk-OGm" id="09I-sr-hnP"/>
|
||||||
<outlet property="visibilityButton" destination="6CJ-b2-876" id="jkz-te-T3u"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</placeholder>
|
</placeholder>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
@ -35,166 +31,179 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="cp4-lI-aME">
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="6Z0-Vy-hMX">
|
||||||
<rect key="frame" x="0.0" y="-44" width="375" height="711"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ovj-HJ-0pD">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
|
||||||
<rect key="frame" x="16" y="16" width="343" height="779"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YTO-vO-WZ5">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="128"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="cOi-cF-kIg">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="width" constant="50" id="IMp-xZ-1wk"/>
|
|
||||||
<constraint firstAttribute="height" constant="50" id="p8i-9U-qdf"/>
|
|
||||||
</constraints>
|
|
||||||
</imageView>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="77P-er-bd4">
|
|
||||||
<rect key="frame" x="58" y="0.0" width="105" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZNM-ey-3j8">
|
|
||||||
<rect key="frame" x="171" y="0.0" width="172" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a2k-Ax-jJd" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="58" y="28.5" width="285" height="99.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="a2k-Ax-jJd" firstAttribute="top" secondItem="77P-er-bd4" secondAttribute="bottom" constant="8" id="6oq-wI-C5d"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="ZNM-ey-3j8" secondAttribute="trailing" id="9v0-fI-wi5"/>
|
|
||||||
<constraint firstItem="cOi-cF-kIg" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="Fnv-I3-PLN"/>
|
|
||||||
<constraint firstItem="cOi-cF-kIg" firstAttribute="leading" secondItem="YTO-vO-WZ5" secondAttribute="leading" id="J0g-iO-Hsy"/>
|
|
||||||
<constraint firstItem="77P-er-bd4" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="anu-L8-kpt"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="a2k-Ax-jJd" secondAttribute="trailing" id="cIX-qN-Bnr"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="a2k-Ax-jJd" secondAttribute="bottom" id="cYK-IY-C6V"/>
|
|
||||||
<constraint firstItem="ZNM-ey-3j8" firstAttribute="leading" secondItem="77P-er-bd4" secondAttribute="trailing" constant="8" id="dV5-U5-sJL"/>
|
|
||||||
<constraint firstItem="77P-er-bd4" firstAttribute="leading" secondItem="cOi-cF-kIg" secondAttribute="trailing" constant="8" id="hva-Nf-90R"/>
|
|
||||||
<constraint firstItem="ZNM-ey-3j8" firstAttribute="top" secondItem="YTO-vO-WZ5" secondAttribute="top" id="oET-4l-974"/>
|
|
||||||
<constraint firstItem="a2k-Ax-jJd" firstAttribute="leading" secondItem="cOi-cF-kIg" secondAttribute="trailing" constant="8" id="rt2-Lx-cgH"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="dpq-un-p7Y">
|
|
||||||
<rect key="frame" x="0.0" y="136" width="343" height="643"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="In reply to Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lZc-Z2-0vV">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="5Yb-vo-0yN">
|
|
||||||
<rect key="frame" x="0.0" y="26.5" width="343" height="200"/>
|
|
||||||
<color key="backgroundColor" red="0.8980392157" green="0.8980392157" blue="0.91764705879999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="200" id="URa-6h-Hbt"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
||||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
|
||||||
</textView>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HTy-eW-OvQ">
|
|
||||||
<rect key="frame" x="0.0" y="232.5" width="343" height="30"/>
|
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pZv-DT-CdW">
|
<view contentMode="scaleToFill" placeholderIntrinsicWidth="infinite" placeholderIntrinsicHeight="66" translatesAutoresizingMaskIntoConstraints="NO" id="zZ3-Gv-4P5" customClass="LargeAccountDetailView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="42" height="30"/>
|
<rect key="frame" x="0.0" y="0.0" width="336" height="66"/>
|
||||||
<state key="normal" title="Media"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<connections>
|
</view>
|
||||||
<action selector="mediaPressed:" destination="-1" eventType="touchUpInside" id="7Uf-WL-RnQ"/>
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="500" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PMB-Wa-Ht0">
|
||||||
</connections>
|
<rect key="frame" x="336" y="8" width="31" height="20.5"/>
|
||||||
</button>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H1c-9o-wdb">
|
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<rect key="frame" x="50" y="0.0" width="30" height="30"/>
|
|
||||||
<state key="normal" title="CW"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="contentWarningPressed:" destination="-1" eventType="touchUpInside" id="MrZ-09-UDE"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6CJ-b2-876">
|
|
||||||
<rect key="frame" x="88" y="0.0" width="42" height="30"/>
|
|
||||||
<state key="normal" title="Public"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="visibilityPressed:" destination="-1" eventType="touchUpInside" id="rg9-0L-AZ9"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" text="500" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Ti-cT-JnH">
|
|
||||||
<rect key="frame" x="138" y="0.0" width="166" height="30"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
|
||||||
<color key="textColor" white="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i4l-LR-1kp">
|
</subviews>
|
||||||
<rect key="frame" x="312" y="0.0" width="31" height="30"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<state key="normal" title="Post"/>
|
<constraints>
|
||||||
<connections>
|
<constraint firstAttribute="trailing" secondItem="PMB-Wa-Ht0" secondAttribute="trailing" constant="8" id="5KJ-rz-Heh"/>
|
||||||
<action selector="postPressed:" destination="-1" eventType="touchUpInside" id="KKB-fz-Xra"/>
|
<constraint firstItem="zZ3-Gv-4P5" firstAttribute="leading" secondItem="6V0-mH-Mhu" secondAttribute="leading" id="f6Q-fK-zq1"/>
|
||||||
</connections>
|
<constraint firstItem="zZ3-Gv-4P5" firstAttribute="top" secondItem="6V0-mH-Mhu" secondAttribute="top" id="fjf-mn-l9f"/>
|
||||||
</button>
|
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="top" secondItem="6V0-mH-Mhu" secondAttribute="top" constant="8" id="q3V-aY-t9K"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="zZ3-Gv-4P5" secondAttribute="bottom" id="rOO-0n-odM"/>
|
||||||
|
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="leading" secondItem="zZ3-Gv-4P5" secondAttribute="trailing" id="sVv-tH-7eB"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kU2-7l-MSy">
|
||||||
|
<rect key="frame" x="0.0" y="66" width="375" height="46"/>
|
||||||
|
<subviews>
|
||||||
|
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write your warning here" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="T05-p6-vTz">
|
||||||
|
<rect key="frame" x="4" y="8" width="367" height="30"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="30" id="yzY-MF-Ukx"/>
|
||||||
|
</constraints>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||||
|
<textInputTraits key="textInputTraits"/>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="T05-p6-vTz" secondAttribute="trailing" constant="4" id="8tG-eW-TG4"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="T05-p6-vTz" secondAttribute="bottom" constant="8" id="SUL-Hk-uvM"/>
|
||||||
|
<constraint firstItem="T05-p6-vTz" firstAttribute="leading" secondItem="kU2-7l-MSy" secondAttribute="leading" constant="4" id="WGG-B2-lPC"/>
|
||||||
|
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="8" id="lvW-S0-4k4"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9">
|
||||||
|
<rect key="frame" x="0.0" y="112" width="375" height="150"/>
|
||||||
|
<subviews>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9pn-0T-IHb">
|
||||||
|
<rect key="frame" x="4" y="0.0" width="367" height="150"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="150" id="ISI-jm-FxV"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="What's on your mind?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EW3-YK-vPC">
|
||||||
|
<rect key="frame" x="8" y="8" width="188" height="24"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||||
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="9pn-0T-IHb" secondAttribute="bottom" id="UAs-fL-Riv"/>
|
||||||
|
<constraint firstItem="9pn-0T-IHb" firstAttribute="leading" secondItem="lhQ-ae-pe9" secondAttribute="leading" constant="4" id="ezI-15-Yd4"/>
|
||||||
|
<constraint firstItem="9pn-0T-IHb" firstAttribute="top" secondItem="lhQ-ae-pe9" secondAttribute="top" id="n8v-pK-I9E"/>
|
||||||
|
<constraint firstItem="EW3-YK-vPC" firstAttribute="leading" secondItem="9pn-0T-IHb" secondAttribute="leading" constant="4" id="n9v-mJ-gz3"/>
|
||||||
|
<constraint firstItem="EW3-YK-vPC" firstAttribute="top" secondItem="9pn-0T-IHb" secondAttribute="top" constant="8" id="q5e-yM-bS4"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="9pn-0T-IHb" secondAttribute="trailing" constant="4" id="x7Z-8w-xgm"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
|
||||||
|
<rect key="frame" x="0.0" y="262" width="375" height="80"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Aqk-LY-jEj">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="1"/>
|
||||||
|
<color key="backgroundColor" red="0.90978598590000004" green="0.90964758400000001" blue="0.91777461770000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="1" id="0C7-KP-bIQ"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cVn-xc-LH9">
|
||||||
|
<rect key="frame" x="0.0" y="79" width="375" height="1"/>
|
||||||
|
<color key="backgroundColor" red="0.90978598590000004" green="0.90964758400000001" blue="0.91777461770000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="1" id="SZ4-5b-Hcf"/>
|
||||||
|
<constraint firstAttribute="height" constant="1" id="VIz-vl-Um4"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eEV-Yt-Njk">
|
||||||
|
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="80" id="sGZ-uD-CtS"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||||
|
<state key="normal" title=" Add image or video" image="More">
|
||||||
|
<color key="titleColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</state>
|
||||||
|
<connections>
|
||||||
|
<action selector="addAttachmentPressed:" destination="-1" eventType="touchUpInside" id="aUR-nx-O9u"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Aqk-LY-jEj" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="6i1-Jt-AEM"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="eEV-Yt-Njk" secondAttribute="bottom" id="C6D-yq-PU3"/>
|
||||||
|
<constraint firstItem="Aqk-LY-jEj" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="Y5a-qr-Dby"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="cVn-xc-LH9" secondAttribute="bottom" id="dCy-ov-086"/>
|
||||||
|
<constraint firstItem="eEV-Yt-Njk" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" constant="8" id="enN-pq-hxK"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Aqk-LY-jEj" secondAttribute="trailing" id="h1Q-QT-wB9"/>
|
||||||
|
<constraint firstItem="cVn-xc-LH9" firstAttribute="leading" secondItem="752-dD-eAO" secondAttribute="leading" id="oNI-gt-O9v"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="eEV-Yt-Njk" secondAttribute="trailing" constant="8" id="qe1-4r-oaa"/>
|
||||||
|
<constraint firstItem="eEV-Yt-Njk" firstAttribute="top" secondItem="752-dD-eAO" secondAttribute="top" id="rpc-rE-Q57"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="cVn-xc-LH9" secondAttribute="trailing" id="uSI-lv-mqY"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<textField hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Content Warning" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="FA4-UR-Pc9">
|
|
||||||
<rect key="frame" x="0.0" y="265.5" width="343" height="0.0"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
|
||||||
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
|
||||||
</textField>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="wHz-h7-939">
|
|
||||||
<rect key="frame" x="0.0" y="268.5" width="343" height="100"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="100" id="0qA-Te-SOn"/>
|
|
||||||
</constraints>
|
|
||||||
</stackView>
|
|
||||||
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="YyO-d2-4bx">
|
|
||||||
<rect key="frame" x="0.0" y="374.5" width="343" height="268.5"/>
|
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="What is on your mind?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="blO-2d-WiC">
|
<constraints>
|
||||||
<rect key="frame" x="20.5" y="186.5" width="170" height="20.5"/>
|
<constraint firstAttribute="trailing" secondItem="bOB-hF-O9w" secondAttribute="trailing" id="GAR-qc-jte"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<constraint firstAttribute="height" secondItem="bOB-hF-O9w" secondAttribute="height" id="KO2-zF-s7P"/>
|
||||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<constraint firstItem="bOB-hF-O9w" firstAttribute="top" secondItem="pcX-rB-RxJ" secondAttribute="top" id="aBm-Ub-TpI"/>
|
||||||
<nil key="highlightedColor"/>
|
<constraint firstItem="bOB-hF-O9w" firstAttribute="leading" secondItem="pcX-rB-RxJ" secondAttribute="leading" id="yOt-hH-L57"/>
|
||||||
</label>
|
</constraints>
|
||||||
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="bottom" secondItem="ovj-HJ-0pD" secondAttribute="bottom" id="GqL-42-cue"/>
|
<constraint firstItem="pcX-rB-RxJ" firstAttribute="leading" secondItem="6Z0-Vy-hMX" secondAttribute="leading" id="X0f-ja-XBF"/>
|
||||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="leading" secondItem="cp4-lI-aME" secondAttribute="leading" constant="16" id="I7Y-uR-bf0"/>
|
<constraint firstAttribute="bottom" secondItem="pcX-rB-RxJ" secondAttribute="bottom" id="X8a-PA-r8F"/>
|
||||||
<constraint firstItem="blO-2d-WiC" firstAttribute="top" secondItem="5Yb-vo-0yN" secondAttribute="top" constant="8" id="Wfi-2X-213"/>
|
<constraint firstAttribute="trailing" secondItem="pcX-rB-RxJ" secondAttribute="trailing" id="lh7-xn-MGp"/>
|
||||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="top" secondItem="cp4-lI-aME" secondAttribute="top" constant="16" id="bq1-LT-laf"/>
|
<constraint firstItem="pcX-rB-RxJ" firstAttribute="top" secondItem="6Z0-Vy-hMX" secondAttribute="top" id="yMM-IS-8K1"/>
|
||||||
<constraint firstItem="blO-2d-WiC" firstAttribute="leading" secondItem="5Yb-vo-0yN" secondAttribute="leading" constant="4.5" id="dhw-kL-D2B"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="ovj-HJ-0pD" secondAttribute="trailing" constant="16" id="hJy-JL-iae"/>
|
|
||||||
<constraint firstItem="dpq-un-p7Y" firstAttribute="height" secondItem="cp4-lI-aME" secondAttribute="height" constant="-68" id="oE5-6c-QYI"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
</scrollView>
|
</scrollView>
|
||||||
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progressViewStyle="bar" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Ncd-80-ldI" customClass="SteppedProgressView" customModule="Tusker" customModuleProvider="target">
|
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Tq7-6P-hMT" customClass="SteppedProgressView" customModule="Tusker" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="20" width="375" height="2.5"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="2"/>
|
||||||
|
<color key="trackTintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</progressView>
|
</progressView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="Ncd-80-ldI" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="0eq-md-iJK"/>
|
<constraint firstAttribute="trailing" secondItem="Tq7-6P-hMT" secondAttribute="trailing" id="GeN-8q-weq"/>
|
||||||
<constraint firstItem="ovj-HJ-0pD" firstAttribute="width" secondItem="7XG-Dk-OGm" secondAttribute="width" constant="-32" id="0sw-Bf-MLK"/>
|
<constraint firstItem="Heg-g4-sYM" firstAttribute="bottom" secondItem="6Z0-Vy-hMX" secondAttribute="bottom" id="Hf3-Cc-mVX"/>
|
||||||
<constraint firstItem="cp4-lI-aME" firstAttribute="top" secondItem="7XG-Dk-OGm" secondAttribute="topMargin" constant="-64" id="7ef-so-AaW"/>
|
<constraint firstItem="Tq7-6P-hMT" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="LgA-xu-VGE"/>
|
||||||
<constraint firstItem="cp4-lI-aME" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="8fs-On-3th"/>
|
<constraint firstItem="Tq7-6P-hMT" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="agM-ZO-c3E"/>
|
||||||
<constraint firstItem="Heg-g4-sYM" firstAttribute="bottom" secondItem="cp4-lI-aME" secondAttribute="bottom" id="S1A-db-SQZ"/>
|
<constraint firstItem="Heg-g4-sYM" firstAttribute="trailing" secondItem="6Z0-Vy-hMX" secondAttribute="trailing" id="hjY-W6-wTQ"/>
|
||||||
<constraint firstItem="Ncd-80-ldI" firstAttribute="leading" secondItem="7XG-Dk-OGm" secondAttribute="leading" id="Wy2-V4-Jkp"/>
|
<constraint firstItem="bOB-hF-O9w" firstAttribute="width" secondItem="7XG-Dk-OGm" secondAttribute="width" id="i0p-NE-ca1"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="cp4-lI-aME" secondAttribute="trailing" id="afV-LD-XZS"/>
|
<constraint firstItem="6Z0-Vy-hMX" firstAttribute="leading" secondItem="Heg-g4-sYM" secondAttribute="leading" id="lFF-yC-ql9"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="Ncd-80-ldI" secondAttribute="trailing" id="k6I-L5-xZc"/>
|
<constraint firstItem="6Z0-Vy-hMX" firstAttribute="top" secondItem="Heg-g4-sYM" secondAttribute="top" id="osv-zq-seP"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<viewLayoutGuide key="safeArea" id="Heg-g4-sYM"/>
|
<viewLayoutGuide key="safeArea" id="Heg-g4-sYM"/>
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="More" width="60" height="30"/>
|
||||||
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
//
|
||||||
|
// StatusFormat.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/12/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
enum StatusFormat: CaseIterable {
|
||||||
|
case italics, bold, strikethrough, code
|
||||||
|
|
||||||
|
var insertionResult: FormatInsertionResult? {
|
||||||
|
switch Preferences.shared.statusContentType {
|
||||||
|
case .plain:
|
||||||
|
return nil
|
||||||
|
case .markdown:
|
||||||
|
return Markdown.format(self)
|
||||||
|
case .html:
|
||||||
|
return HTML.format(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: (String, [NSAttributedString.Key: Any]) {
|
||||||
|
switch self {
|
||||||
|
case .italics:
|
||||||
|
return ("I", [.font: UIFont.italicSystemFont(ofSize: 17)])
|
||||||
|
case .bold:
|
||||||
|
return ("B", [.font: UIFont.boldSystemFont(ofSize: 17)])
|
||||||
|
case .strikethrough:
|
||||||
|
return ("S", [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue)])
|
||||||
|
case .code:
|
||||||
|
return ("</>", [.font: UIFont(name: "Menlo", size: 17)!])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias FormatInsertionResult = (prefix: String, suffix: String, insertionPoint: Int)
|
||||||
|
|
||||||
|
protocol FormatType {
|
||||||
|
static func format(_ format: StatusFormat) -> FormatInsertionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusFormat {
|
||||||
|
struct Markdown: FormatType {
|
||||||
|
static var formats: [StatusFormat: String] = [
|
||||||
|
.italics: "_",
|
||||||
|
.bold: "**",
|
||||||
|
.strikethrough: "~~",
|
||||||
|
.code: "`"
|
||||||
|
]
|
||||||
|
|
||||||
|
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
||||||
|
let str = formats[format]!
|
||||||
|
return (str, str, str.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HTML: FormatType {
|
||||||
|
static var tags: [StatusFormat: String] = [
|
||||||
|
.italics: "em",
|
||||||
|
.bold: "strong",
|
||||||
|
.strikethrough: "del",
|
||||||
|
.code: "code"
|
||||||
|
]
|
||||||
|
|
||||||
|
static func format(_ format: StatusFormat) -> FormatInsertionResult {
|
||||||
|
let tag = tags[format]!
|
||||||
|
return ("<\(tag)>", "</\(tag)>", tag.count + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// AdvancedTableViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/12/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class AdvancedTableViewController: UITableViewController {
|
||||||
|
|
||||||
|
@IBOutlet weak var postContentTypeLabel: UILabel!
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
postContentTypeLabel.text = Preferences.shared.statusContentType.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
guard indexPath.section == 1 && indexPath.row == 0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: "Post Content Type", message: nil, preferredStyle: .actionSheet)
|
||||||
|
for contentType in StatusContentType.allCases {
|
||||||
|
let action = UIAlertAction(title: contentType.displayName, style: .default) { (_) in
|
||||||
|
Preferences.shared.statusContentType = contentType
|
||||||
|
self.postContentTypeLabel.text = contentType.displayName
|
||||||
|
}
|
||||||
|
if contentType == Preferences.shared.statusContentType {
|
||||||
|
action.setValue(true, forKey: "checked")
|
||||||
|
}
|
||||||
|
alertController.addAction(action)
|
||||||
|
}
|
||||||
|
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||||
|
present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusContentType {
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .plain:
|
||||||
|
return "Plain"
|
||||||
|
case .markdown:
|
||||||
|
return "Markdown"
|
||||||
|
case .html:
|
||||||
|
return "HTML"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VJJ-jC-9g8">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VJJ-jC-9g8">
|
||||||
<device id="retina4_7" orientation="portrait">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
|
@ -311,7 +311,7 @@
|
||||||
<!--Advanced-->
|
<!--Advanced-->
|
||||||
<scene sceneID="xgj-Fx-53j">
|
<scene sceneID="xgj-Fx-53j">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="3mv-l7-6We" sceneMemberID="viewController">
|
<tableViewController id="3mv-l7-6We" customClass="AdvancedTableViewController" customModule="Tusker" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="hcR-ju-th0">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="hcR-ju-th0">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
@ -344,6 +344,39 @@
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</tableViewSection>
|
</tableViewSection>
|
||||||
|
<tableViewSection id="aNt-ow-hHp">
|
||||||
|
<string key="footerTitle">This option is only supported for Pleroma instances with formatting enabled. On all other instances, formatting symbols will remain in the plain, unformatted text.</string>
|
||||||
|
<cells>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="L56-bm-5R5">
|
||||||
|
<rect key="frame" x="0.0" y="135.5" width="375" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="L56-bm-5R5" id="mtr-Qa-CSI">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Post Content Type" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ThO-WW-QsH">
|
||||||
|
<rect key="frame" x="16" y="11.5" width="142" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Plain" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uq5-Fi-mpp">
|
||||||
|
<rect key="frame" x="322" y="11.5" width="37" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="uq5-Fi-mpp" firstAttribute="centerY" secondItem="mtr-Qa-CSI" secondAttribute="centerY" id="BMn-9K-DXz"/>
|
||||||
|
<constraint firstItem="ThO-WW-QsH" firstAttribute="centerY" secondItem="mtr-Qa-CSI" secondAttribute="centerY" id="p7P-du-z4q"/>
|
||||||
|
<constraint firstItem="ThO-WW-QsH" firstAttribute="leading" secondItem="mtr-Qa-CSI" secondAttribute="leadingMargin" id="upT-aD-0JW"/>
|
||||||
|
<constraint firstItem="uq5-Fi-mpp" firstAttribute="trailing" secondItem="mtr-Qa-CSI" secondAttribute="trailingMargin" id="vW4-hc-eeu"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
</tableViewCell>
|
||||||
|
</cells>
|
||||||
|
</tableViewSection>
|
||||||
</sections>
|
</sections>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="3mv-l7-6We" id="Ots-vG-253"/>
|
<outlet property="dataSource" destination="3mv-l7-6We" id="Ots-vG-253"/>
|
||||||
|
@ -351,6 +384,9 @@
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Advanced" id="Rav-Eq-k22"/>
|
<navigationItem key="navigationItem" title="Advanced" id="Rav-Eq-k22"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="postContentTypeLabel" destination="uq5-Fi-mpp" id="LN1-20-FMA"/>
|
||||||
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="4wC-Hp-AZk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="4wC-Hp-AZk" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// LargeAccountDetailViewController.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/6/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class LargeAccountDetailView: UIView, PreferencesAdaptive {
|
||||||
|
|
||||||
|
var avatarImageView = UIImageView()
|
||||||
|
var displayNameLabel = UILabel()
|
||||||
|
var usernameLabel = UILabel()
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
|
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
avatarImageView.layer.masksToBounds = true
|
||||||
|
addSubview(avatarImageView)
|
||||||
|
|
||||||
|
displayNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
displayNameLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||||
|
addSubview(displayNameLabel)
|
||||||
|
|
||||||
|
usernameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
usernameLabel.font = .systemFont(ofSize: 17, weight: .light)
|
||||||
|
usernameLabel.textColor = .darkGray
|
||||||
|
addSubview(usernameLabel)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarImageView.heightAnchor.constraint(equalToConstant: 50),
|
||||||
|
avatarImageView.widthAnchor.constraint(equalToConstant: 50),
|
||||||
|
avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
||||||
|
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||||
|
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
|
||||||
|
displayNameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||||
|
displayNameLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||||
|
usernameLabel.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||||
|
usernameLabel.topAnchor.constraint(equalTo: displayNameLabel.bottomAnchor)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
updateUIForPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIForPreferences() {
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(account: Account) {
|
||||||
|
displayNameLabel.text = account.realDisplayName
|
||||||
|
usernameLabel.text = "@\(account.acct)"
|
||||||
|
|
||||||
|
ImageCache.avatars.get(account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// ComposeAttachmentView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/10/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Photos
|
||||||
|
|
||||||
|
protocol ComposeMediaViewDelegate {
|
||||||
|
func didRemoveMedia(_ mediaView: ComposeMediaView)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComposeMediaView: UIView {
|
||||||
|
|
||||||
|
var delegate: ComposeMediaViewDelegate?
|
||||||
|
|
||||||
|
@IBOutlet weak var imageView: UIImageView!
|
||||||
|
@IBOutlet weak var descriptionTextView: UITextView!
|
||||||
|
@IBOutlet weak var placeholderLabel: UILabel!
|
||||||
|
|
||||||
|
static func create() -> ComposeMediaView {
|
||||||
|
return UINib(nibName: "ComposeMediaView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeMediaView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
imageView.layer.cornerRadius = 10 // 0.1 * imageView.frame.width
|
||||||
|
|
||||||
|
descriptionTextView.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(asset: PHAsset) {
|
||||||
|
// let size = imageView.frame.size // is this initialized yet?
|
||||||
|
let size = CGSize(width: 80, height: 80)
|
||||||
|
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||||
|
self.imageView.image = image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Interaction
|
||||||
|
|
||||||
|
@IBAction func removePressed(_ sender: Any) {
|
||||||
|
delegate?.didRemoveMedia(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ComposeMediaView: UITextViewDelegate {
|
||||||
|
func textViewDidChange(_ textView: UITextView) {
|
||||||
|
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina4_7" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||||
|
<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"/>
|
||||||
|
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ComposeMediaView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="u7I-sx-kUe">
|
||||||
|
<rect key="frame" x="8" y="293.5" width="80" height="80"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="80" id="CgF-eC-We6"/>
|
||||||
|
<constraint firstAttribute="width" constant="80" id="S3g-yM-TRb"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G1g-Lw-Ren">
|
||||||
|
<rect key="frame" x="345" y="322.5" width="22" height="22"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="22" id="dyG-5Y-91s"/>
|
||||||
|
<constraint firstAttribute="width" constant="22" id="sWQ-3z-Z5r"/>
|
||||||
|
</constraints>
|
||||||
|
<state key="normal" image="Remove"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="removePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="aor-Cq-YjJ"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="O6b-Zs-u8r">
|
||||||
|
<rect key="frame" x="96" y="20" width="241" height="647"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="80" id="GsE-uM-fhe"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Describe for the visually impaired..." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rkD-NP-09H">
|
||||||
|
<rect key="frame" x="100" y="28" width="233" height="41"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0h4-wv-2R8">
|
||||||
|
<rect key="frame" x="0.0" y="20" width="375" height="1"/>
|
||||||
|
<color key="backgroundColor" red="0.90978598594665527" green="0.90964758396148682" blue="0.91777461767196655" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="1" id="aQa-2T-uYY"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="O6b-Zs-u8r" secondAttribute="bottom" id="3sv-wo-gxe"/>
|
||||||
|
<constraint firstItem="u7I-sx-kUe" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="5Qs-i7-glv"/>
|
||||||
|
<constraint firstItem="u7I-sx-kUe" firstAttribute="top" relation="greaterThanOrEqual" secondItem="0h4-wv-2R8" secondAttribute="bottom" constant="8" id="86L-Cb-Lsk"/>
|
||||||
|
<constraint firstItem="O6b-Zs-u8r" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="9QY-MR-Yc2"/>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="0h4-wv-2R8" secondAttribute="trailing" id="FCS-un-JT6"/>
|
||||||
|
<constraint firstItem="u7I-sx-kUe" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="UWq-Lf-zGB"/>
|
||||||
|
<constraint firstItem="rkD-NP-09H" firstAttribute="leading" secondItem="O6b-Zs-u8r" secondAttribute="leading" constant="4" id="aQd-T9-n1I"/>
|
||||||
|
<constraint firstItem="0h4-wv-2R8" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="edp-4a-YGq"/>
|
||||||
|
<constraint firstItem="G1g-Lw-Ren" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="hJA-JF-MHp"/>
|
||||||
|
<constraint firstItem="O6b-Zs-u8r" firstAttribute="leading" secondItem="u7I-sx-kUe" secondAttribute="trailing" constant="8" id="hvZ-m4-TOV"/>
|
||||||
|
<constraint firstItem="G1g-Lw-Ren" firstAttribute="leading" secondItem="O6b-Zs-u8r" secondAttribute="trailing" constant="8" id="ith-0X-3Yz"/>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="u7I-sx-kUe" secondAttribute="bottom" constant="8" id="lGN-Qg-mBO"/>
|
||||||
|
<constraint firstItem="rkD-NP-09H" firstAttribute="top" secondItem="O6b-Zs-u8r" secondAttribute="top" constant="8" id="mRb-3M-7uz"/>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="G1g-Lw-Ren" secondAttribute="trailing" constant="8" id="o4F-MV-ahd"/>
|
||||||
|
<constraint firstItem="0h4-wv-2R8" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="sFo-wp-MYP"/>
|
||||||
|
<constraint firstItem="O6b-Zs-u8r" firstAttribute="trailing" secondItem="rkD-NP-09H" secondAttribute="trailing" constant="4" id="vwg-7l-8ca"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="descriptionTextView" destination="O6b-Zs-u8r" id="TNy-7h-0sY"/>
|
||||||
|
<outlet property="imageView" destination="u7I-sx-kUe" id="o3a-O0-m85"/>
|
||||||
|
<outlet property="placeholderLabel" destination="rkD-NP-09H" id="WtV-2h-L3n"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="136.80000000000001" y="142.57871064467767"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="Remove" width="81" height="81"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// ComposeStatusReplyView.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 1/6/19.
|
||||||
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class ComposeStatusReplyView: UIView, PreferencesAdaptive {
|
||||||
|
|
||||||
|
@IBOutlet weak var avatarImageView: UIImageView!
|
||||||
|
@IBOutlet weak var displayNameLabel: UILabel!
|
||||||
|
@IBOutlet weak var usernameLabel: UILabel!
|
||||||
|
@IBOutlet weak var contentLabel: StatusContentLabel!
|
||||||
|
|
||||||
|
static func create() -> ComposeStatusReplyView {
|
||||||
|
return UINib(nibName: "ComposeStatusReplyView", bundle: nil).instantiate(withOwner: nil, options: nil).first as! ComposeStatusReplyView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
updateUIForPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIForPreferences() {
|
||||||
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUI(for status: Status) {
|
||||||
|
displayNameLabel.text = status.account.realDisplayName
|
||||||
|
usernameLabel.text = "@\(status.account.acct)"
|
||||||
|
contentLabel.statusID = status.id
|
||||||
|
|
||||||
|
ImageCache.avatars.get(status.account.avatar) { (data) in
|
||||||
|
guard let data = data else { return }
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.30.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina4_7" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.19.1"/>
|
||||||
|
<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"/>
|
||||||
|
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ComposeStatusReplyView" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ypn-Ed-MTq">
|
||||||
|
<rect key="frame" x="8" y="28" width="50" height="50"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="50" id="8qi-gl-5ci"/>
|
||||||
|
<constraint firstAttribute="width" constant="50" id="Dy2-jh-AJj"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2cE-sS-Uut">
|
||||||
|
<rect key="frame" x="66" y="28" width="301" height="631"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Display name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sdv-dB-Plm">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="@username" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0yZ-71-eTj">
|
||||||
|
<rect key="frame" x="115" y="0.0" width="178" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Content" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OEF-Hj-v3f" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="24.5" width="301" height="606.5"/>
|
||||||
|
<fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Sdv-dB-Plm" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="6v5-7p-9gm"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="OEF-Hj-v3f" secondAttribute="bottom" id="IEQ-Ab-tsP"/>
|
||||||
|
<constraint firstItem="OEF-Hj-v3f" firstAttribute="top" secondItem="Sdv-dB-Plm" secondAttribute="bottom" constant="4" id="J5s-TU-odB"/>
|
||||||
|
<constraint firstItem="Sdv-dB-Plm" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="YmP-yU-sfe"/>
|
||||||
|
<constraint firstItem="OEF-Hj-v3f" firstAttribute="leading" secondItem="2cE-sS-Uut" secondAttribute="leading" id="bbW-07-e2x"/>
|
||||||
|
<constraint firstItem="0yZ-71-eTj" firstAttribute="top" secondItem="2cE-sS-Uut" secondAttribute="top" id="bdX-ge-bMT"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="0yZ-71-eTj" secondAttribute="trailing" constant="8" id="hU7-aZ-ibI"/>
|
||||||
|
<constraint firstItem="0yZ-71-eTj" firstAttribute="leading" secondItem="Sdv-dB-Plm" secondAttribute="trailing" constant="8" id="m0X-YU-m3V"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="OEF-Hj-v3f" secondAttribute="trailing" id="xqX-4X-lJl"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="2cE-sS-Uut" firstAttribute="height" relation="greaterThanOrEqual" secondItem="Ypn-Ed-MTq" secondAttribute="height" id="Fn3-o4-RGx"/>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="2cE-sS-Uut" secondAttribute="bottom" constant="8" id="G2d-Kz-c4e"/>
|
||||||
|
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="MbW-9d-3gC"/>
|
||||||
|
<constraint firstItem="2cE-sS-Uut" firstAttribute="leading" secondItem="Ypn-Ed-MTq" secondAttribute="trailing" constant="8" id="TS2-Sr-PB3"/>
|
||||||
|
<constraint firstItem="2cE-sS-Uut" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="cat-Cr-PSV"/>
|
||||||
|
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="2cE-sS-Uut" secondAttribute="trailing" constant="8" id="eH4-lG-5UR"/>
|
||||||
|
<constraint firstItem="Ypn-Ed-MTq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" placeholder="YES" id="xCn-8G-jUZ"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="avatarImageView" destination="Ypn-Ed-MTq" id="eea-bc-klc"/>
|
||||||
|
<outlet property="contentLabel" destination="OEF-Hj-v3f" id="GBI-ib-5T0"/>
|
||||||
|
<outlet property="displayNameLabel" destination="Sdv-dB-Plm" id="RxW-Ra-Ups"/>
|
||||||
|
<outlet property="usernameLabel" destination="0yZ-71-eTj" id="VQm-Dq-3zP"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="138.40000000000001" y="-72.863568215892059"/>
|
||||||
|
</view>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -1,57 +0,0 @@
|
||||||
//
|
|
||||||
// ComposeAttachmentView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 8/30/18.
|
|
||||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
protocol ComposeMediaViewDelegate {
|
|
||||||
func editDescription(for media: ComposeMediaView)
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposeMediaView: UIImageView {
|
|
||||||
|
|
||||||
var delegate: ComposeMediaViewDelegate?
|
|
||||||
|
|
||||||
var remove: UIImageView
|
|
||||||
|
|
||||||
var mediaDescription: String?
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
init(image: UIImage) {
|
|
||||||
remove = UIImageView(image: UIImage(named: "Remove"))
|
|
||||||
|
|
||||||
super.init(image: image)
|
|
||||||
|
|
||||||
contentMode = .scaleAspectFill
|
|
||||||
layer.cornerRadius = 5
|
|
||||||
layer.masksToBounds = true
|
|
||||||
isUserInteractionEnabled = true
|
|
||||||
addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressed)))
|
|
||||||
|
|
||||||
remove.isUserInteractionEnabled = true
|
|
||||||
remove.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(removePressed)))
|
|
||||||
remove.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(remove)
|
|
||||||
|
|
||||||
remove.widthAnchor.constraint(equalToConstant: 20).isActive = true
|
|
||||||
remove.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
|
||||||
remove.topAnchor.constraint(equalTo: topAnchor, constant: 5).isActive = true
|
|
||||||
remove.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func removePressed() {
|
|
||||||
removeFromSuperview()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func longPressed() {
|
|
||||||
delegate?.editDescription(for: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue