Browse Source

Replace GMImagePicker with custom asset picker based on SheetController

Fixes #23
Closes #50
simple-swift-promises
Shadowfacts 6 months ago
parent
commit
3ecbb1895c
Signed by: Shadowfacts <me@shadowfacts.net> GPG Key ID: 94A5AB95422746E5
47 changed files with 1407 additions and 2565 deletions
  1. BIN
      GMImagePicker/Base.lproj/GMImagePicker.strings
  2. 0
    32
      GMImagePicker/GMAlbumsViewCell.h
  3. 0
    131
      GMImagePicker/GMAlbumsViewCell.m
  4. 0
    32
      GMImagePicker/GMAlbumsViewController.h
  5. 0
    416
      GMImagePicker/GMAlbumsViewController.m
  6. BIN
      GMImagePicker/GMEmptyFolder@1x.png
  7. BIN
      GMImagePicker/GMEmptyFolder@2x.png
  8. 0
    32
      GMImagePicker/GMGridViewCell.h
  9. 0
    176
      GMImagePicker/GMGridViewCell.m
  10. 0
    21
      GMImagePicker/GMGridViewController.h
  11. 0
    611
      GMImagePicker/GMGridViewController.m
  12. 0
    24
      GMImagePicker/GMImagePicker.h
  13. 0
    332
      GMImagePicker/GMImagePickerController.h
  14. 0
    388
      GMImagePicker/GMImagePickerController.m
  15. BIN
      GMImagePicker/GMSelected.png
  16. BIN
      GMImagePicker/GMSelected@2x.png
  17. BIN
      GMImagePicker/GMVideoIcon.png
  18. BIN
      GMImagePicker/GMVideoIcon@2x.png
  19. 0
    22
      GMImagePicker/Info.plist
  20. BIN
      GMImagePicker/ca.lproj/GMImagePicker.strings
  21. BIN
      GMImagePicker/de.lproj/GMImagePicker.strings
  22. BIN
      GMImagePicker/en.lproj/GMImagePicker.strings
  23. BIN
      GMImagePicker/es.lproj/GMImagePicker.strings
  24. BIN
      GMImagePicker/fr.lproj/GMImagePicker.strings
  25. BIN
      GMImagePicker/it.lproj/GMImagePicker.strings
  26. BIN
      GMImagePicker/pt.lproj/GMImagePicker.strings
  27. 98
    256
      Tusker.xcodeproj/project.pbxproj
  28. 16
    0
      Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved
  29. 1
    2
      Tusker/DraftsManager.swift
  30. 3
    1
      Tusker/Info.plist
  31. 30
    0
      Tusker/Screens/Compose/Asset Picker/AlbumAssetCollectionViewController.swift
  32. 186
    0
      Tusker/Screens/Compose/Asset Picker/AssetCollectionViewController.swift
  33. 134
    0
      Tusker/Screens/Compose/Asset Picker/AssetCollectionsListViewController.swift
  34. 68
    0
      Tusker/Screens/Compose/Asset Picker/AssetPickerSheetContainerViewController.swift
  35. 95
    0
      Tusker/Screens/Compose/Asset Picker/AssetPickerViewController.swift
  36. 118
    0
      Tusker/Screens/Compose/Asset Picker/AssetPreviewViewController.swift
  37. 36
    76
      Tusker/Screens/Compose/ComposeViewController.swift
  38. 183
    0
      Tusker/Screens/Compose/CompositionAttachment.swift
  39. 10
    7
      Tusker/Screens/Compose/Drafts/DraftTableViewCell.swift
  40. 42
    0
      Tusker/Views/Asset Picker/AlbumTableViewCell.swift
  41. 50
    0
      Tusker/Views/Asset Picker/AlbumTableViewCell.xib
  42. 37
    0
      Tusker/Views/Asset Picker/AllPhotosTableViewCell.swift
  43. 49
    0
      Tusker/Views/Asset Picker/AllPhotosTableViewCell.xib
  44. 74
    0
      Tusker/Views/Asset Picker/AssetCollectionViewCell.swift
  45. 121
    0
      Tusker/Views/Asset Picker/AssetCollectionViewCell.xib
  46. 37
    0
      Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib
  47. 19
    6
      Tusker/Views/Compose Media/ComposeMediaView.swift

BIN
GMImagePicker/Base.lproj/GMImagePicker.strings View File


+ 0
- 32
GMImagePicker/GMAlbumsViewCell.h View File

@@ -1,32 +0,0 @@
1
-//
2
-//  GMAlbumsViewCell.h
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 22/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#include <UIKit/UIKit.h>
10
-#include <Photos/Photos.h>
11
-
12
-@interface GMAlbumsViewCell : UITableViewCell
13
-
14
-@property (strong) PHFetchResult *assetsFetchResults;
15
-@property (strong) PHAssetCollection *assetCollection;
16
-
17
-//The labels
18
-@property (nonatomic, strong) UILabel *titleLabel;
19
-@property (nonatomic, strong) UILabel *infoLabel;
20
-//The imageView
21
-@property (nonatomic, strong) UIImageView *imageView1;
22
-@property (nonatomic, strong) UIImageView *imageView2;
23
-@property (nonatomic, strong) UIImageView *imageView3;
24
-//Video additional information
25
-@property (nonatomic, strong) UIImageView *videoIcon;
26
-@property (nonatomic, strong) UIImageView *slowMoIcon;
27
-@property (nonatomic, strong) UIView *gradientView;
28
-@property (nonatomic, strong) CAGradientLayer *gradient;
29
-//Selection overlay
30
-
31
-- (void)setVideoLayout:(BOOL)isVideo;
32
-@end

+ 0
- 131
GMImagePicker/GMAlbumsViewCell.m View File

@@ -1,131 +0,0 @@
1
-//
2
-//  GMAlbumsViewCell.m
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 22/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import "GMAlbumsViewCell.h"
10
-#import "GMAlbumsViewController.h"
11
-#import "GMImagePickerController.h"
12
-#import <QuartzCore/QuartzCore.h>
13
-
14
-@implementation GMAlbumsViewCell
15
-
16
-- (void)awakeFromNib
17
-{
18
-  [super awakeFromNib];
19
-}
20
-
21
-- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
22
-{
23
-  
24
-  if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
25
-  {
26
-    // self.isAccessibilityElement             = YES;
27
-      self.contentView.backgroundColor = [UIColor clearColor];
28
-      self.backgroundColor = [UIColor clearColor];
29
-    self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
30
-    
31
-    // Border width of 1 pixel:
32
-    float borderWidth = 1.0/[UIScreen mainScreen].scale;
33
-    
34
-    // ImageView
35
-    _imageView3 = [UIImageView new];
36
-    _imageView3.contentMode = UIViewContentModeScaleAspectFill;
37
-    _imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
38
-    [_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
39
-    [_imageView3.layer setBorderWidth: borderWidth];
40
-    _imageView3.clipsToBounds = YES;
41
-    _imageView3.translatesAutoresizingMaskIntoConstraints = YES;
42
-    _imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
43
-    [self.contentView addSubview:_imageView3];
44
-    
45
-    // ImageView
46
-    _imageView2 = [UIImageView new];
47
-    _imageView2.contentMode = UIViewContentModeScaleAspectFill;
48
-    _imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
49
-    [_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
50
-    [_imageView2.layer setBorderWidth: borderWidth];
51
-    _imageView2.clipsToBounds = YES;
52
-    _imageView2.translatesAutoresizingMaskIntoConstraints = YES;
53
-    _imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
54
-    [self.contentView addSubview:_imageView2];
55
-    
56
-    // ImageView
57
-    _imageView1 = [UIImageView new];
58
-    _imageView1.contentMode = UIViewContentModeScaleAspectFill;
59
-    _imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
60
-    [_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
61
-    [_imageView1.layer setBorderWidth: borderWidth];
62
-    _imageView1.clipsToBounds = YES;
63
-    _imageView1.translatesAutoresizingMaskIntoConstraints = YES;
64
-    _imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
65
-    [self.contentView addSubview:_imageView1];
66
-    
67
-    
68
-    // The video gradient, label & icon
69
-    UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
70
-    UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
71
-    UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
72
-    _gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
73
-    _gradient = [CAGradientLayer layer];
74
-    _gradient.frame = _gradientView.bounds;
75
-    _gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
76
-    _gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
77
-    [_gradientView.layer insertSublayer:_gradient atIndex:0];
78
-    _gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
79
-    _gradientView.translatesAutoresizingMaskIntoConstraints = YES;
80
-    [self.imageView1 addSubview:_gradientView];
81
-    _gradientView.hidden = YES;
82
-    
83
-    // VideoIcon
84
-    _videoIcon = [UIImageView new];
85
-    _videoIcon.contentMode = UIViewContentModeScaleAspectFill;
86
-    _videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
87
-    _videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMAlbumsViewCell.class] compatibleWithTraitCollection:nil];
88
-    _videoIcon.clipsToBounds = YES;
89
-    _videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
90
-    _videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
91
-    [self.imageView1 addSubview:_videoIcon];
92
-    _videoIcon.hidden = NO;
93
-    
94
-    // TextLabel
95
-    self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
96
-    self.textLabel.numberOfLines = 1;
97
-    
98
-    self.detailTextLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
99
-    self.detailTextLabel.numberOfLines = 1;
100
-    
101
-  }
102
-  return self;
103
-}
104
-
105
--(void)layoutSubviews {
106
-  [super layoutSubviews];
107
-  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);
108
-  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);
109
-  
110
-}
111
-- (void)setVideoLayout:(BOOL)isVideo
112
-{
113
-  // TODO : Add additional icons for slowmo, burst, etc...
114
-  if (isVideo) {
115
-    _videoIcon.hidden = NO;
116
-    _gradientView.hidden = NO;
117
-  } else {
118
-    _videoIcon.hidden = YES;
119
-    _gradientView.hidden = YES;
120
-  }
121
-}
122
-
123
-
124
-- (void)setSelected:(BOOL)selected animated:(BOOL)animated
125
-{
126
-  [super setSelected:selected animated:animated];
127
-  
128
-  // Configure the view for the selected state
129
-}
130
-
131
-@end

+ 0
- 32
GMImagePicker/GMAlbumsViewController.h View File

@@ -1,32 +0,0 @@
1
-//
2
-//  GMAlbumsViewController.h
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import <UIKit/UIKit.h>
10
-
11
-// Measuring IOS8 Photos APP at @2x (iPhone5s):
12
-//   The rows are 180px/90pts
13
-//   Left image border is 21px/10.5pts
14
-//   Separation between image and text is 42px/21pts (double the previouse one)
15
-//   The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
16
-//   The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
17
-//   The third image measures 123px/61.5pts  including 1px/0.5pts white border. Only 3px/1.5pts visible
18
-
19
-static int kAlbumRowHeight = 90;
20
-static int kAlbumLeftToImageSpace = 10;
21
-static int kAlbumImageToTextSpace = 21;
22
-static float const kAlbumGradientHeight = 20.0f;
23
-static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
24
-static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
25
-static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
26
-
27
-
28
-@interface GMAlbumsViewController : UITableViewController
29
-
30
-- (void)selectAllAlbumsCell;
31
-
32
-@end

+ 0
- 416
GMImagePicker/GMAlbumsViewController.m View File

@@ -1,416 +0,0 @@
1
-//
2
-//  GMAlbumsViewController.m
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import "GMImagePickerController.h"
10
-#import "GMAlbumsViewController.h"
11
-#import "GMGridViewCell.h"
12
-#import "GMGridViewController.h"
13
-#import "GMAlbumsViewCell.h"
14
-
15
-#include <Photos/Photos.h>
16
-
17
-@interface GMAlbumsViewController() <PHPhotoLibraryChangeObserver>
18
-
19
-@property (strong,nonatomic) NSArray *collectionsFetchResults;
20
-@property (strong,nonatomic) NSArray *collectionsLocalizedTitles;
21
-@property (strong,nonatomic) NSArray *collectionsFetchResultsAssets;
22
-@property (strong,nonatomic) NSArray *collectionsFetchResultsTitles;
23
-@property (nonatomic, weak) GMImagePickerController *picker;
24
-@property (strong,nonatomic) PHCachingImageManager *imageManager;
25
-
26
-@end
27
-
28
-
29
-@implementation GMAlbumsViewController
30
-
31
-- (id)init
32
-{
33
-  if (self = [super initWithStyle:UITableViewStylePlain]) {
34
-    self.preferredContentSize = kPopoverContentSize;
35
-  }
36
-  
37
-  return self;
38
-}
39
-
40
-static NSString *const AllPhotosReuseIdentifier = @"AllPhotosCell";
41
-static NSString *const CollectionCellReuseIdentifier = @"CollectionCell";
42
-
43
-- (void)viewDidLoad
44
-{
45
-  [super viewDidLoad];
46
-  
47
-  self.view.backgroundColor = [self.picker pickerBackgroundColor];
48
-  
49
-  // Navigation bar customization
50
-  if (self.picker.customNavigationBarPrompt) {
51
-    self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
52
-  }
53
-  
54
-  self.imageManager = [[PHCachingImageManager alloc] init];
55
-  
56
-  // Table view aspect
57
-  self.tableView.rowHeight = kAlbumRowHeight;
58
-  self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
59
-  
60
-  // Buttons
61
-  NSDictionary *barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
62
-  
63
-  NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
64
-  self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
65
-                                                                           style:UIBarButtonItemStylePlain
66
-                                                                          target:self.picker
67
-                                                                          action:@selector(dismiss:)];
68
-  if (self.picker.useCustomFontForNavigationBar) {
69
-    [self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
70
-    [self.navigationItem.leftBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
71
-  }
72
-  
73
-  if (self.picker.allowsMultipleSelection) {
74
-    NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
75
-    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
76
-                                                                              style:UIBarButtonItemStyleDone
77
-                                                                             target:self.picker
78
-                                                                             action:@selector(finishPickingAssets:)];
79
-    if (self.picker.useCustomFontForNavigationBar) {
80
-      [self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
81
-      [self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
82
-    }
83
-    
84
-    self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
85
-  }
86
-  
87
-  // Bottom toolbar
88
-  self.toolbarItems = self.picker.toolbarItems;
89
-  
90
-  // Title
91
-  if (!self.picker.title) {
92
-    self.title = NSLocalizedStringFromTableInBundle(@"picker.navigation.title",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Navigation bar default title");
93
-  } else {
94
-    self.title = self.picker.title;
95
-  }
96
-  
97
-  // Fetch PHAssetCollections:
98
-  PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
99
-  PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
100
-  self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
101
-  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")];
102
-  
103
-  [self updateFetchResults];
104
-  
105
-  // Register for changes
106
-  [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
107
-  
108
-  if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
109
-  {
110
-    self.edgesForExtendedLayout = UIRectEdgeNone;
111
-  }
112
-}
113
-
114
-- (void)dealloc
115
-{
116
-  [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
117
-}
118
-
119
-- (UIStatusBarStyle)preferredStatusBarStyle {
120
-  return self.picker.pickerStatusBarStyle;
121
-}
122
-
123
-- (void)selectAllAlbumsCell {
124
-  NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
125
-  [self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
126
-}
127
-
128
--(void)updateFetchResults
129
-{
130
-  //What I do here is fetch both the albums list and the assets of each album.
131
-  //This way I have acces to the number of items in each album, I can load the 3
132
-  //thumbnails directly and I can pass the fetched result to the gridViewController.
133
-  
134
-  self.collectionsFetchResultsAssets=nil;
135
-  self.collectionsFetchResultsTitles=nil;
136
-  
137
-  //Fetch PHAssetCollections:
138
-  PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
139
-  PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
140
-  
141
-  //All album: Sorted by descending creation date.
142
-  NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
143
-  NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
144
-  {
145
-    PHFetchOptions *options = [[PHFetchOptions alloc] init];
146
-    options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
147
-    options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
148
-    PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
149
-    [allFetchResultArray addObject:assetsFetchResult];
150
-    [allFetchResultLabel addObject:NSLocalizedStringFromTableInBundle(@"picker.table.all-photos-label",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"All photos")];
151
-  }
152
-  
153
-  //User albums:
154
-  NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
155
-  NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
156
-  for(PHCollection *collection in topLevelUserCollections)
157
-  {
158
-    if ([collection isKindOfClass:[PHAssetCollection class]])
159
-    {
160
-      PHFetchOptions *options = [[PHFetchOptions alloc] init];
161
-      options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
162
-      PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
163
-      
164
-      //Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
165
-      
166
-      PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
167
-      [userFetchResultArray addObject:assetsFetchResult];
168
-      [userFetchResultLabel addObject:collection.localizedTitle];
169
-    }
170
-  }
171
-  
172
-  
173
-  //Smart albums: Sorted by descending creation date.
174
-  NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
175
-  NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
176
-  for(PHCollection *collection in smartAlbums)
177
-  {
178
-    if ([collection isKindOfClass:[PHAssetCollection class]])
179
-    {
180
-      PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
181
-      
182
-      //Smart collections are PHAssetCollectionType=2;
183
-      if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
184
-      {
185
-        PHFetchOptions *options = [[PHFetchOptions alloc] init];
186
-        options.predicate = [NSPredicate predicateWithFormat:@"mediaType in %@", self.picker.mediaTypes];
187
-        options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
188
-        
189
-        PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
190
-        if(assetsFetchResult.count>0)
191
-        {
192
-          [smartFetchResultArray addObject:assetsFetchResult];
193
-          [smartFetchResultLabel addObject:collection.localizedTitle];
194
-        }
195
-      }
196
-    }
197
-  }
198
-  
199
-  self.collectionsFetchResultsAssets= @[allFetchResultArray,smartFetchResultArray,userFetchResultArray];
200
-  self.collectionsFetchResultsTitles= @[allFetchResultLabel,smartFetchResultLabel,userFetchResultLabel];
201
-}
202
-
203
-
204
-#pragma mark - Accessors
205
-
206
-- (GMImagePickerController *)picker
207
-{
208
-  return (GMImagePickerController *)self.navigationController.parentViewController;
209
-}
210
-
211
-
212
-#pragma mark - Rotation
213
-
214
-- (BOOL)shouldAutorotate
215
-{
216
-  return YES;
217
-}
218
-
219
-- (UIInterfaceOrientationMask)supportedInterfaceOrientations
220
-{
221
-  return UIInterfaceOrientationMaskAllButUpsideDown;
222
-}
223
-
224
-
225
-#pragma mark - UITableViewDataSource
226
-
227
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
228
-{
229
-  return (NSInteger)self.collectionsFetchResultsAssets.count;
230
-}
231
-
232
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
233
-{
234
-  PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
235
-  return (NSInteger)fetchResult.count;
236
-}
237
-
238
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
239
-{
240
-  static NSString *CellIdentifier = @"Cell";
241
-  
242
-  GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
243
-  if (cell == nil) {
244
-    cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
245
-    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
246
-  }
247
-  
248
-  // Increment the cell's tag
249
-  NSInteger currentTag = cell.tag + 1;
250
-  cell.tag = currentTag;
251
-  
252
-  // Set the label
253
-  cell.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize];
254
-  cell.textLabel.text = (self.collectionsFetchResultsTitles[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
255
-  cell.textLabel.textColor = self.picker.pickerTextColor;
256
-  
257
-  // Retrieve the pre-fetched assets for this album:
258
-  PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[(NSUInteger)indexPath.section])[(NSUInteger)indexPath.row];
259
-  
260
-  // Display the number of assets
261
-  if (self.picker.displayAlbumsNumberOfAssets) {
262
-    cell.detailTextLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
263
-    cell.detailTextLabel.text = [self tableCellSubtitle:assetsFetchResult];
264
-    cell.detailTextLabel.textColor = self.picker.pickerTextColor;
265
-  }
266
-  
267
-  // Set the 3 images (if exists):
268
-  if ([assetsFetchResult count] > 0) {
269
-    CGFloat scale = [UIScreen mainScreen].scale;
270
-    
271
-    //Compute the thumbnail pixel size:
272
-    CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
273
-    PHAsset *asset = assetsFetchResult[0];
274
-    [cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
275
-    [self.imageManager requestImageForAsset:asset
276
-                                 targetSize:tableCellThumbnailSize1
277
-                                contentMode:PHImageContentModeAspectFill
278
-                                    options:nil
279
-                              resultHandler:^(UIImage *result, NSDictionary *info) {
280
-                                if (cell.tag == currentTag) {
281
-                                  cell.imageView1.image = result;
282
-                                }
283
-                              }];
284
-    
285
-    // Second & third images:
286
-    // TODO: Only preload the 3pixels height visible frame!
287
-    if ([assetsFetchResult count] > 1) {
288
-      //Compute the thumbnail pixel size:
289
-      CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
290
-      PHAsset *asset = assetsFetchResult[1];
291
-      [self.imageManager requestImageForAsset:asset
292
-                                   targetSize:tableCellThumbnailSize2
293
-                                  contentMode:PHImageContentModeAspectFill
294
-                                      options:nil
295
-                                resultHandler:^(UIImage *result, NSDictionary *info) {
296
-                                  if (cell.tag == currentTag) {
297
-                                    cell.imageView2.image = result;
298
-                                  }
299
-                                }];
300
-    } else {
301
-      cell.imageView2.image = nil;
302
-    }
303
-    
304
-    if ([assetsFetchResult count] > 2) {
305
-      CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
306
-      PHAsset *asset = assetsFetchResult[2];
307
-      [self.imageManager requestImageForAsset:asset
308
-                                   targetSize:tableCellThumbnailSize3
309
-                                  contentMode:PHImageContentModeAspectFill
310
-                                      options:nil
311
-                                resultHandler:^(UIImage *result, NSDictionary *info) {
312
-                                  if (cell.tag == currentTag) {
313
-                                    cell.imageView3.image = result;
314
-                                  }
315
-                                }];
316
-    } else {
317
-      cell.imageView3.image = nil;
318
-    }
319
-  } else {
320
-    [cell setVideoLayout:NO];
321
-    cell.imageView3.image = [UIImage imageNamed:@"GMEmptyFolder"];
322
-    cell.imageView2.image = [UIImage imageNamed:@"GMEmptyFolder"];
323
-    cell.imageView1.image = [UIImage imageNamed:@"GMEmptyFolder"];
324
-  }
325
-  
326
-  return cell;
327
-}
328
-
329
-#pragma mark - UITableViewDelegate
330
-
331
-- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
332
-{
333
-  UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
334
-  
335
-  // Init the GMGridViewController
336
-  GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
337
-  // Set the title
338
-  gridViewController.title = cell.textLabel.text;
339
-  // Use the prefetched assets!
340
-  gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:(NSUInteger)indexPath.section] objectAtIndex:(NSUInteger)indexPath.row];
341
-  
342
-  // Remove selection so it looks better on slide in
343
-  [tableView deselectRowAtIndexPath:indexPath animated:true];
344
-  
345
-  // Push GMGridViewController
346
-  [self.navigationController pushViewController:gridViewController animated:YES];
347
-}
348
-
349
--(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
350
-{
351
-  UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
352
-  //    header.contentView.backgroundColor = [UIColor clearColor];
353
-  //    header.backgroundView.backgroundColor = [UIColor clearColor];
354
-  
355
-  // Default is a bold font, but keep this styled as a normal font
356
-  header.textLabel.font = [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontNormalSize];
357
-  header.textLabel.textColor = self.picker.pickerTextColor;
358
-}
359
-
360
-- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
361
-{
362
-  //Tip: Returning nil hides the section header!
363
-  
364
-  NSString *title = nil;
365
-  if (section > 0) {
366
-    // Only show title for non-empty sections:
367
-    PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[(NSUInteger)section];
368
-    if (fetchResult.count > 0) {
369
-      title = self.collectionsLocalizedTitles[(NSUInteger)(section - 1)];
370
-    }
371
-  }
372
-  return title;
373
-}
374
-
375
-
376
-#pragma mark - PHPhotoLibraryChangeObserver
377
-
378
-- (void)photoLibraryDidChange:(PHChange *)changeInstance
379
-{
380
-  // Call might come on any background queue. Re-dispatch to the main queue to handle it.
381
-  dispatch_async(dispatch_get_main_queue(), ^{
382
-    
383
-    NSMutableArray *updatedCollectionsFetchResults = nil;
384
-    
385
-    for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
386
-      PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
387
-      if (changeDetails) {
388
-        if (!updatedCollectionsFetchResults) {
389
-          updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
390
-        }
391
-        [updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
392
-      }
393
-    }
394
-    
395
-    // This only affects to changes in albums level (add/remove/edit album)
396
-    if (updatedCollectionsFetchResults) {
397
-      self.collectionsFetchResults = updatedCollectionsFetchResults;
398
-    }
399
-    
400
-    // However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
401
-    // Maybe some checks could be done here , but for now is OKey.
402
-    [self updateFetchResults];
403
-    [self.tableView reloadData];
404
-    
405
-  });
406
-}
407
-
408
-#pragma mark - Cell Subtitle
409
-
410
-- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
411
-{
412
-  // Just return the number of assets. Album app does this:
413
-  return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
414
-}
415
-
416
-@end

BIN
GMImagePicker/GMEmptyFolder@1x.png View File


BIN
GMImagePicker/GMEmptyFolder@2x.png View File


+ 0
- 32
GMImagePicker/GMGridViewCell.h View File

@@ -1,32 +0,0 @@
1
-//
2
-//  GMGridViewCell.h
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#include <UIKit/UIKit.h>
10
-#include <Photos/Photos.h>
11
-
12
-
13
-@interface GMGridViewCell : UICollectionViewCell
14
-
15
-@property (nonatomic, strong) PHAsset *asset;
16
-//The imageView
17
-@property (nonatomic, strong) UIImageView *imageView;
18
-//Video additional information
19
-@property (nonatomic, strong) UIImageView *videoIcon;
20
-@property (nonatomic, strong) UILabel *videoDuration;
21
-@property (nonatomic, strong) UIView *gradientView;
22
-@property (nonatomic, strong) CAGradientLayer *gradient;
23
-//Selection overlay
24
-@property (nonatomic) BOOL shouldShowSelection;
25
-@property (nonatomic, strong) UIView *coverView;
26
-@property (nonatomic, strong) UIButton *selectedButton;
27
-
28
-@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
29
-
30
-- (void)bind:(PHAsset *)asset;
31
-
32
-@end

+ 0
- 176
GMImagePicker/GMGridViewCell.m View File

@@ -1,176 +0,0 @@
1
-//
2
-//  GMGridViewCell.m
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import "GMGridViewCell.h"
10
-
11
-
12
-@interface GMGridViewCell ()
13
-@end
14
-
15
-
16
-@implementation GMGridViewCell
17
-
18
-static UIFont *titleFont;
19
-static CGFloat titleHeight;
20
-static UIImage *videoIcon;
21
-static UIColor *titleColor;
22
-static UIImage *checkedIcon;
23
-static UIColor *selectedColor;
24
-static UIColor *disabledColor;
25
-
26
-+ (void)initialize
27
-{
28
-  titleFont       = [UIFont systemFontOfSize:12];
29
-  titleHeight     = 20.0f;
30
-  videoIcon       = [UIImage imageNamed:@"GMImagePickerVideo"];
31
-  titleColor      = [UIColor whiteColor];
32
-  checkedIcon     = [UIImage imageNamed:@"CTAssetsPickerChecked"];
33
-  selectedColor   = [UIColor colorWithWhite:1 alpha:0.3];
34
-  disabledColor   = [UIColor colorWithWhite:1 alpha:0.9];
35
-}
36
-
37
-- (void)awakeFromNib
38
-{
39
-  [super awakeFromNib];
40
-  
41
-  self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
42
-  self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
43
-}
44
-
45
-- (id)initWithFrame:(CGRect)frame
46
-{
47
-  if (self = [super initWithFrame:frame]) {
48
-    self.opaque                 = NO;
49
-    self.enabled                = YES;
50
-    
51
-    CGFloat cellSize = self.contentView.bounds.size.width;
52
-    
53
-    // The image view
54
-    _imageView = [UIImageView new];
55
-    _imageView.frame = CGRectMake(0, 0, cellSize, cellSize);
56
-    _imageView.contentMode = UIViewContentModeScaleAspectFill;
57
-    /*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
58
-     {
59
-     _imageView.contentMode = UIViewContentModeScaleAspectFit;
60
-     }
61
-     else
62
-     {
63
-     _imageView.contentMode = UIViewContentModeScaleAspectFill;
64
-     }*/
65
-    _imageView.clipsToBounds = YES;
66
-    _imageView.translatesAutoresizingMaskIntoConstraints = NO;
67
-    _imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
68
-    [self addSubview:_imageView];
69
-    
70
-    
71
-    // The video gradient, label & icon
72
-    float x_offset = 4.0f;
73
-    UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
74
-    UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.8];
75
-    _gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, self.bounds.size.height-titleHeight, self.bounds.size.width, titleHeight)];
76
-    _gradient = [CAGradientLayer layer];
77
-    _gradient.frame = _gradientView.bounds;
78
-    _gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[botGradient CGColor], nil];
79
-    [_gradientView.layer insertSublayer:_gradient atIndex:0];
80
-    _gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
81
-    _gradientView.translatesAutoresizingMaskIntoConstraints = NO;
82
-    [self addSubview:_gradientView];
83
-    _gradientView.hidden = YES;
84
-    
85
-    _videoIcon = [UIImageView new];
86
-    _videoIcon.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
87
-    _videoIcon.contentMode = UIViewContentModeLeft;
88
-    _videoIcon.image = [UIImage imageNamed:@"GMVideoIcon" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil];
89
-    _videoIcon.translatesAutoresizingMaskIntoConstraints = NO;
90
-    _videoIcon.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
91
-    [self addSubview:_videoIcon];
92
-    _videoIcon.hidden = YES;
93
-    
94
-    _videoDuration = [UILabel new];
95
-    _videoDuration.font = titleFont;
96
-    _videoDuration.textColor = titleColor;
97
-    _videoDuration.textAlignment = NSTextAlignmentRight;
98
-    _videoDuration.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
99
-    _videoDuration.contentMode = UIViewContentModeRight;
100
-    _videoDuration.translatesAutoresizingMaskIntoConstraints = NO;
101
-    _videoDuration.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
102
-    [self addSubview:_videoDuration];
103
-    _videoDuration.hidden = YES;
104
-    
105
-    // Selection overlay & icon
106
-    _coverView = [[UIView alloc] initWithFrame:self.bounds];
107
-    _coverView.translatesAutoresizingMaskIntoConstraints = NO;
108
-    _coverView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
109
-    _coverView.backgroundColor = [UIColor colorWithRed:0.24 green:0.47 blue:0.85 alpha:0.6];
110
-    [self addSubview:_coverView];
111
-    _coverView.hidden = YES;
112
-    
113
-    _selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
114
-    _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);
115
-    _selectedButton.contentMode = UIViewContentModeTopRight;
116
-    _selectedButton.adjustsImageWhenHighlighted = NO;
117
-    [_selectedButton setImage:nil forState:UIControlStateNormal];
118
-    _selectedButton.translatesAutoresizingMaskIntoConstraints = NO;
119
-    _selectedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
120
-    [_selectedButton setImage:[UIImage imageNamed:@"GMSelected" inBundle:[NSBundle bundleForClass:GMGridViewCell.class] compatibleWithTraitCollection:nil] forState:UIControlStateSelected];
121
-    _selectedButton.hidden = NO;
122
-    _selectedButton.userInteractionEnabled = NO;
123
-    [self addSubview:_selectedButton];
124
-  }
125
-  
126
-  // Note: the views above are created in case this is toggled per cell, on the fly, etc.!
127
-  self.shouldShowSelection = YES;
128
-  
129
-  return self;
130
-}
131
-
132
-// Required to resize the CAGradientLayer because it does not support auto resizing.
133
-- (void)layoutSubviews {
134
-  [super layoutSubviews];
135
-  _gradient.frame = _gradientView.bounds;
136
-}
137
-
138
-- (void)bind:(PHAsset *)asset
139
-{
140
-  self.asset  = asset;
141
-  
142
-  if (self.asset.mediaType == PHAssetMediaTypeVideo) {
143
-    _videoIcon.hidden = NO;
144
-    _videoDuration.hidden = NO;
145
-    _gradientView.hidden = NO;
146
-    _videoDuration.text = [self getDurationWithFormat:self.asset.duration];
147
-  } else {
148
-    _videoIcon.hidden = YES;
149
-    _videoDuration.hidden = YES;
150
-    _gradientView.hidden = YES;
151
-  }
152
-}
153
-
154
-// Override setSelected
155
-- (void)setSelected:(BOOL)selected
156
-{
157
-  [super setSelected:selected];
158
-  
159
-  if (!self.shouldShowSelection) {
160
-    return;
161
-  }
162
-  
163
-  _coverView.hidden = !selected;
164
-  _selectedButton.selected = selected;
165
-}
166
-
167
--(NSString*)getDurationWithFormat:(NSTimeInterval)duration
168
-{
169
-  NSInteger ti = (NSInteger)duration;
170
-  NSInteger seconds = ti % 60;
171
-  NSInteger minutes = (ti / 60) % 60;
172
-  //NSInteger hours = (ti / 3600);
173
-  return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
174
-}
175
-
176
-@end

+ 0
- 21
GMImagePicker/GMGridViewController.h View File

@@ -1,21 +0,0 @@
1
-//
2
-//  GMGridViewController.h
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-
10
-#import "GMImagePickerController.h"
11
-#include <UIKit/UIKit.h>
12
-#include <Photos/Photos.h>
13
-
14
-
15
-@interface GMGridViewController : UICollectionViewController
16
-
17
-@property (strong,nonatomic) PHFetchResult *assetsFetchResults;
18
-
19
--(id)initWithPicker:(GMImagePickerController *)picker;
20
-
21
-@end

+ 0
- 611
GMImagePicker/GMGridViewController.m View File

@@ -1,611 +0,0 @@
1
-//
2
-//  GMGridViewController.m
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import "GMGridViewController.h"
10
-#import "GMImagePickerController.h"
11
-#import "GMAlbumsViewController.h"
12
-#import "GMGridViewCell.h"
13
-
14
-#include <Photos/Photos.h>
15
-
16
-
17
-//Helper methods
18
-@implementation NSIndexSet (Convenience)
19
-- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
20
-  NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
21
-  [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
22
-    [indexPaths addObject:[NSIndexPath indexPathForItem:(NSInteger)idx inSection:(NSInteger)section]];
23
-  }];
24
-  return indexPaths;
25
-}
26
-@end
27
-
28
-@implementation UICollectionView (Convenience)
29
-- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
30
-  NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
31
-  if (allLayoutAttributes.count == 0) { return nil; }
32
-  NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
33
-  for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
34
-    NSIndexPath *indexPath = layoutAttributes.indexPath;
35
-    [indexPaths addObject:indexPath];
36
-  }
37
-  return indexPaths;
38
-}
39
-@end
40
-
41
-
42
-
43
-@interface GMImagePickerController ()
44
-
45
-- (void)finishPickingAssets:(id)sender;
46
-- (void)dismiss:(id)sender;
47
-- (NSString *)toolbarTitle;
48
-- (UIView *)noAssetsView;
49
-
50
-@end
51
-
52
-
53
-@interface GMGridViewController () <PHPhotoLibraryChangeObserver>
54
-
55
-@property (nonatomic, weak) GMImagePickerController *picker;
56
-@property (strong,nonatomic) PHCachingImageManager *imageManager;
57
-@property (assign, nonatomic) CGRect previousPreheatRect;
58
-
59
-@end
60
-
61
-static CGSize AssetGridThumbnailSize;
62
-NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
63
-
64
-@implementation GMGridViewController
65
-{
66
-  CGFloat screenWidth;
67
-  CGFloat screenHeight;
68
-  UICollectionViewFlowLayout *portraitLayout;
69
-  UICollectionViewFlowLayout *landscapeLayout;
70
-}
71
-
72
--(id)initWithPicker:(GMImagePickerController *)picker
73
-{
74
-  //Custom init. The picker contains custom information to create the FlowLayout
75
-  self.picker = picker;
76
-  
77
-  //Ipad popover is not affected by rotation!
78
-  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
79
-  {
80
-    screenWidth = CGRectGetWidth(picker.view.bounds);
81
-    screenHeight = CGRectGetHeight(picker.view.bounds);
82
-  }
83
-  else
84
-  {
85
-    if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
86
-    {
87
-      screenHeight = CGRectGetWidth(picker.view.bounds);
88
-      screenWidth = CGRectGetHeight(picker.view.bounds);
89
-    }
90
-    else
91
-    {
92
-      screenWidth = CGRectGetWidth(picker.view.bounds);
93
-      screenHeight = CGRectGetHeight(picker.view.bounds);
94
-    }
95
-  }
96
-  
97
-  
98
-  UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
99
-  if (self = [super initWithCollectionViewLayout:layout])
100
-  {
101
-    //Compute the thumbnail pixel size:
102
-    CGFloat scale = [UIScreen mainScreen].scale;
103
-    //NSLog(@"This is @%fx scale device", scale);
104
-    if(scale >= 3)
105
-    {
106
-      scale = 2;
107
-    }
108
-      
109
-    AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
110
-    
111
-    self.collectionView.allowsMultipleSelection = picker.allowsMultipleSelection;
112
-    
113
-    [self.collectionView registerClass:GMGridViewCell.class
114
-            forCellWithReuseIdentifier:GMGridViewCellIdentifier];
115
-    
116
-    self.preferredContentSize = kPopoverContentSize;
117
-  }
118
-  
119
-  return self;
120
-}
121
-
122
-
123
-- (void)viewDidLoad
124
-{
125
-  [super viewDidLoad];
126
-  [self setupViews];
127
-  
128
-  // Navigation bar customization
129
-  if (self.picker.customNavigationBarPrompt) {
130
-    self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
131
-  }
132
-  
133
-  self.imageManager = [[PHCachingImageManager alloc] init];
134
-  [self resetCachedAssets];
135
-  [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
136
-  
137
-  if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
138
-  {
139
-    self.edgesForExtendedLayout = UIRectEdgeNone;
140
-  }
141
-}
142
-
143
-- (void)viewWillAppear:(BOOL)animated
144
-{
145
-  [super viewWillAppear:animated];
146
-  
147
-  [self setupButtons];
148
-  [self setupToolbar];
149
-}
150
-
151
-- (void)viewDidAppear:(BOOL)animated
152
-{
153
-  [super viewDidAppear:animated];
154
-  [self updateCachedAssets];
155
-}
156
-
157
-- (void)dealloc
158
-{
159
-  [self resetCachedAssets];
160
-  [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
161
-}
162
-
163
-- (UIStatusBarStyle)preferredStatusBarStyle {
164
-  return self.picker.pickerStatusBarStyle;
165
-}
166
-
167
-
168
-#pragma mark - Rotation
169
-
170
-- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
171
-{
172
-  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
173
-    return;
174
-  }
175
-  
176
-  UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
177
-  
178
-  //Update the AssetGridThumbnailSize:
179
-  CGFloat scale = [UIScreen mainScreen].scale;
180
-  AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
181
-  
182
-  [self resetCachedAssets];
183
-  //This is optional. Reload visible thumbnails:
184
-  for (GMGridViewCell *cell in [self.collectionView visibleCells]) {
185
-    NSInteger currentTag = cell.tag;
186
-    [self.imageManager requestImageForAsset:cell.asset
187
-                                 targetSize:AssetGridThumbnailSize
188
-                                contentMode:PHImageContentModeAspectFill
189
-                                    options:nil
190
-                              resultHandler:^(UIImage *result, NSDictionary *info)
191
-     {
192
-       // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
193
-       if (cell.tag == currentTag) {
194
-         [cell.imageView setImage:result];
195
-       }
196
-     }];
197
-  }
198
-  
199
-  [self.collectionView setCollectionViewLayout:layout animated:YES];
200
-}
201
-
202
-
203
-#pragma mark - Setup
204
-
205
-- (void)setupViews
206
-{
207
-  self.collectionView.backgroundColor = [UIColor clearColor];
208
-  self.view.backgroundColor = [self.picker pickerBackgroundColor];
209
-}
210
-
211
-- (void)setupButtons
212
-{
213
-  if (self.picker.allowsMultipleSelection) {
214
-    NSString *doneTitle = self.picker.customDoneButtonTitle ? self.picker.customDoneButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.done-button",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Done");
215
-    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:doneTitle
216
-                                                                              style:UIBarButtonItemStyleDone
217
-                                                                             target:self.picker
218
-                                                                             action:@selector(finishPickingAssets:)];
219
-    
220
-    self.navigationItem.rightBarButtonItem.enabled = (self.picker.autoDisableDoneButton ? self.picker.selectedAssets.count > 0 : TRUE);
221
-  } else {
222
-    NSString *cancelTitle = self.picker.customCancelButtonTitle ? self.picker.customCancelButtonTitle : NSLocalizedStringFromTableInBundle(@"picker.navigation.cancel-button",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class], @"Cancel");
223
-    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:cancelTitle
224
-                                                                              style:UIBarButtonItemStyleDone
225
-                                                                             target:self.picker
226
-                                                                             action:@selector(dismiss:)];
227
-  }
228
-  if (self.picker.useCustomFontForNavigationBar) {
229
-    if (self.picker.useCustomFontForNavigationBar) {
230
-      NSDictionary* barButtonItemAttributes = @{NSFontAttributeName: [UIFont fontWithName:self.picker.pickerFontName size:self.picker.pickerFontHeaderSize]};
231
-      [self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateNormal];
232
-      [self.navigationItem.rightBarButtonItem setTitleTextAttributes:barButtonItemAttributes forState:UIControlStateSelected];
233
-    }
234
-  }
235
-  
236
-}
237
-
238
-- (void)setupToolbar
239
-{
240
-  self.toolbarItems = self.picker.toolbarItems;
241
-}
242
-
243
-
244
-#pragma mark - Collection View Layout
245
-
246
-- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
247
-{
248
-  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
249
-  {
250
-    if(!portraitLayout)
251
-    {
252
-      portraitLayout = [[UICollectionViewFlowLayout alloc] init];
253
-      portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
254
-      int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing);
255
-      portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
256
-      double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
257
-      double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
258
-      double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
259
-      portraitLayout.minimumLineSpacing = spaceWidth;
260
-    }
261
-    return portraitLayout;
262
-  }
263
-  else
264
-  {
265
-    if(UIInterfaceOrientationIsLandscape(orientation))
266
-    {
267
-      if(!landscapeLayout)
268
-      {
269
-        landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
270
-        landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
271
-        int cellTotalUsableWidth = (int)(screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing);
272
-        landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
273
-        double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
274
-        double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
275
-        double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
276
-        landscapeLayout.minimumLineSpacing = spaceWidth;
277
-      }
278
-      return landscapeLayout;
279
-    }
280
-    else
281
-    {
282
-      if(!portraitLayout)
283
-      {
284
-        portraitLayout = [[UICollectionViewFlowLayout alloc] init];
285
-        portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
286
-        int cellTotalUsableWidth = (int)(screenWidth - (self.picker.colsInPortrait-1) * self.picker.minimumInteritemSpacing);
287
-        portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
288
-        double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
289
-        double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
290
-        double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
291
-        portraitLayout.minimumLineSpacing = spaceWidth;
292
-      }
293
-      return portraitLayout;
294
-    }
295
-  }
296
-}
297
-
298
-
299
-#pragma mark - Collection View Data Source
300
-
301
-- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
302
-{
303
-  return 1;
304
-}
305
-
306
-
307
-- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
308
-{
309
-  GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
310
-                                                                   forIndexPath:indexPath];
311
-  
312
-  // Increment the cell's tag
313
-  NSInteger currentTag = cell.tag + 1;
314
-  cell.tag = currentTag;
315
-  
316
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
317
-  
318
-  /*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
319
-   {
320
-   NSLog(@"Image manager: Requesting FIT image for iPad");
321
-   [self.imageManager requestImageForAsset:asset
322
-   targetSize:AssetGridThumbnailSize
323
-   contentMode:PHImageContentModeAspectFit
324
-   options:nil
325
-   resultHandler:^(UIImage *result, NSDictionary *info) {
326
-   
327
-   // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
328
-   if (cell.tag == currentTag) {
329
-   [cell.imageView setImage:result];
330
-   }
331
-   }];
332
-   }
333
-   else*/
334
-  {
335
-    //NSLog(@"Image manager: Requesting FILL image for iPhone");
336
-    [self.imageManager requestImageForAsset:asset
337
-                                 targetSize:AssetGridThumbnailSize
338
-                                contentMode:PHImageContentModeAspectFill
339
-                                    options:nil
340
-                              resultHandler:^(UIImage *result, NSDictionary *info) {
341
-                                
342
-                                // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
343
-                                if (cell.tag == currentTag) {
344
-                                  [cell.imageView setImage:result];
345
-                                }
346
-                              }];
347
-  }
348
-  
349
-  
350
-  [cell bind:asset];
351
-  
352
-  cell.shouldShowSelection = self.picker.allowsMultipleSelection;
353
-  
354
-  // Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
355
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)]) {
356
-    cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
357
-  } else {
358
-    cell.enabled = YES;
359
-  }
360
-  
361
-  // Setting `selected` property blocks further deselection. Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
362
-  if ([self.picker.selectedAssets containsObject:asset]) {
363
-    cell.selected = YES;
364
-    [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
365
-  } else {
366
-    cell.selected = NO;
367
-  }
368
-  
369
-  return cell;
370
-}
371
-
372
-
373
-#pragma mark - Collection View Delegate
374
-
375
-- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
376
-{
377
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
378
-  
379
-  GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
380
-  
381
-  if (!cell.isEnabled) {
382
-    return NO;
383
-  } else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)]) {
384
-    return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
385
-  } else {
386
-    return YES;
387
-  }
388
-}
389
-
390
-- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
391
-{
392
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
393
-  
394
-  [self.picker selectAsset:asset];
395
-  
396
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)]) {
397
-    [self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
398
-  }
399
-}
400
-
401
-- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
402
-{
403
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
404
-  
405
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) {
406
-    return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
407
-  } else {
408
-    return YES;
409
-  }
410
-}
411
-
412
-- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
413
-{
414
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
415
-  
416
-  [self.picker deselectAsset:asset];
417
-  
418
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)]) {
419
-    [self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
420
-  }
421
-}
422
-
423
-- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
424
-{
425
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
426
-  
427
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) {
428
-    return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
429
-  } else {
430
-    return YES;
431
-  }
432
-}
433
-
434
-- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
435
-{
436
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
437
-  
438
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) {
439
-    [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
440
-  }
441
-}
442
-
443
-- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
444
-{
445
-  PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
446
-  
447
-  if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) {
448
-    [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
449
-  }
450
-}
451
-
452
-
453
-
454
-#pragma mark - UICollectionViewDataSource
455
-
456
-- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
457
-{
458
-  NSInteger count = (NSInteger)self.assetsFetchResults.count;
459
-  return count;
460
-}
461
-
462
-
463
-#pragma mark - PHPhotoLibraryChangeObserver
464
-
465
-- (void)photoLibraryDidChange:(PHChange *)changeInstance
466
-{
467
-  // Call might come on any background queue. Re-dispatch to the main queue to handle it.
468
-  dispatch_async(dispatch_get_main_queue(), ^{
469
-    
470
-    // check if there are changes to the assets (insertions, deletions, updates)
471
-    PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
472
-    if (collectionChanges) {
473
-      
474
-      // get the new fetch result
475
-      self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
476
-      
477
-      UICollectionView *collectionView = self.collectionView;
478
-      
479
-      if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
480
-        // we need to reload all if the incremental diffs are not available
481
-        [collectionView reloadData];
482
-        
483
-      } else {
484
-        // if we have incremental diffs, tell the collection view to animate insertions and deletions
485
-        [collectionView performBatchUpdates:^{
486
-          NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
487
-          if ([removedIndexes count]) {
488
-            [collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
489
-          }
490
-          NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
491
-          if ([insertedIndexes count]) {
492
-            [collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
493
-            if (self.picker.showCameraButton && self.picker.autoSelectCameraImages) {
494
-              for (NSIndexPath *path in [insertedIndexes aapl_indexPathsFromIndexesWithSection:0]) {
495
-                [self collectionView:collectionView didSelectItemAtIndexPath:path];
496
-              }
497
-            }
498
-          }
499
-          NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
500
-          if ([changedIndexes count]) {
501
-            [collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
502
-          }
503
-        } completion:NULL];
504
-      }
505
-      
506
-      [self resetCachedAssets];
507
-    }
508
-  });
509
-}
510
-
511
-
512
-#pragma mark - UIScrollViewDelegate
513
-
514
-- (void)scrollViewDidScroll:(UIScrollView *)scrollView
515
-{
516
-  [self updateCachedAssets];
517
-}
518
-
519
-
520
-#pragma mark - Asset Caching
521
-
522
-- (void)resetCachedAssets
523
-{
524
-  [self.imageManager stopCachingImagesForAllAssets];
525
-  self.previousPreheatRect = CGRectZero;
526
-}
527
-
528
-- (void)updateCachedAssets
529
-{
530
-  BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
531
-  if (!isViewVisible) { return; }
532
-  
533
-  // The preheat window is twice the height of the visible rect
534
-  CGRect preheatRect = self.collectionView.bounds;
535
-  preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
536
-  
537
-  // If scrolled by a "reasonable" amount...
538
-  CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
539
-  if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
540
-    
541
-    // Compute the assets to start caching and to stop caching.
542
-    NSMutableArray *addedIndexPaths = [NSMutableArray array];
543
-    NSMutableArray *removedIndexPaths = [NSMutableArray array];
544
-    
545
-    [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
546
-      NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
547
-      [removedIndexPaths addObjectsFromArray:indexPaths];
548
-    } addedHandler:^(CGRect addedRect) {
549
-      NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
550
-      [addedIndexPaths addObjectsFromArray:indexPaths];
551
-    }];
552
-    
553
-    NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
554
-    NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
555
-    
556
-    [self.imageManager startCachingImagesForAssets:assetsToStartCaching
557
-                                        targetSize:AssetGridThumbnailSize
558
-                                       contentMode:PHImageContentModeAspectFill
559
-                                           options:nil];
560
-    [self.imageManager stopCachingImagesForAssets:assetsToStopCaching
561
-                                       targetSize:AssetGridThumbnailSize
562
-                                      contentMode:PHImageContentModeAspectFill
563
-                                          options:nil];
564
-    
565
-    self.previousPreheatRect = preheatRect;
566
-  }
567
-}
568
-
569
-- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
570
-{
571
-  if (CGRectIntersectsRect(newRect, oldRect)) {
572
-    CGFloat oldMaxY = CGRectGetMaxY(oldRect);
573
-    CGFloat oldMinY = CGRectGetMinY(oldRect);
574
-    CGFloat newMaxY = CGRectGetMaxY(newRect);
575
-    CGFloat newMinY = CGRectGetMinY(newRect);
576
-    if (newMaxY > oldMaxY) {
577
-      CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
578
-      addedHandler(rectToAdd);
579
-    }
580
-    if (oldMinY > newMinY) {
581
-      CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
582
-      addedHandler(rectToAdd);
583
-    }
584
-    if (newMaxY < oldMaxY) {
585
-      CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
586
-      removedHandler(rectToRemove);
587
-    }
588
-    if (oldMinY < newMinY) {
589
-      CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
590
-      removedHandler(rectToRemove);
591
-    }
592
-  } else {
593
-    addedHandler(newRect);
594
-    removedHandler(oldRect);
595
-  }
596
-}
597
-
598
-- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
599
-{
600
-  if (indexPaths.count == 0) { return nil; }
601
-  
602
-  NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
603
-  for (NSIndexPath *indexPath in indexPaths) {
604
-    PHAsset *asset = self.assetsFetchResults[(NSUInteger)indexPath.item];
605
-    [assets addObject:asset];
606
-  }
607
-  return assets;
608
-}
609
-
610
-
611
-@end

+ 0
- 24
GMImagePicker/GMImagePicker.h View File

@@ -1,24 +0,0 @@
1
-//
2
-//  GMImagePicker.h
3
-//  GMImagePicker
4
-//
5
-//  Created by Shadowfacts on 1/14/19.
6
-//  Copyright © 2019 Shadowfacts. All rights reserved.
7
-//
8
-
9
-#import <UIKit/UIKit.h>
10
-
11
-//! Project version number for GMImagePicker.
12
-FOUNDATION_EXPORT double GMImagePickerVersionNumber;
13
-
14
-//! Project version string for GMImagePicker.
15
-FOUNDATION_EXPORT const unsigned char GMImagePickerVersionString[];
16
-
17
-// In this header, you should import all the public headers of your framework using statements like #import <GMImagePicker/PublicHeader.h>
18
-
19
-
20
-#import <GMImagePicker/GMImagePickerController.h>
21
-#import <GMImagePicker/GMAlbumsViewCell.h>
22
-#import <GMImagePicker/GMAlbumsViewController.h>
23
-#import <GMImagePicker/GMGridViewCell.h>
24
-#import <GMImagePicker/GMGridViewController.h>

+ 0
- 332
GMImagePicker/GMImagePickerController.h View File

@@ -1,332 +0,0 @@
1
-//
2
-//  GMImagePickerController.h
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import <UIKit/UIKit.h>
10
-#import <Photos/Photos.h>
11
-
12
-
13
-//This is the default image picker size!
14
-//static CGSize const kPopoverContentSize = {320, 480};
15
-//However, the iPad is 1024x768 so it can allow popups up to 768!
16
-static CGSize const kPopoverContentSize = {480, 720};
17
-
18
-
19
-@protocol GMImagePickerControllerDelegate;
20
-
21
-
22
-/**
23
- *  A controller that allows picking multiple photos and videos from user's photo library.
24
- */
25
-@interface GMImagePickerController : UIViewController
26
-
27
-/**
28
- *  The assets picker’s delegate object.
29
- */
30
-@property (nonatomic, weak) id <GMImagePickerControllerDelegate> delegate;
31
-
32
-/**
33
- *  It contains the selected `PHAsset` objects. The order of the objects is the selection order.
34
- *
35
- *  You can add assets before presenting the picker to show the user some preselected assets.
36
- */
37
-@property (nonatomic, strong) NSMutableArray *selectedAssets;
38
-
39
-
40
-/** UI Customizations **/
41
-
42
-/**
43
- *  Determines which smart collections are displayed (int array of enum: PHAssetCollectionSubtypeSmartAlbum)
44
- *  The default smart collections are:
45
- *  - Favorites
46
- *  - RecentlyAdded
47
- *  - Videos
48
- *  - SlomoVideos
49
- *  - Timelapses
50
- *  - Bursts
51
- *  - Panoramas
52
- */
53
-@property (nonatomic, strong) NSArray* customSmartCollections;
54
-
55
-/**
56
- *  Determines which media types are allowed (int array of enum: PHAssetMediaType)
57
- *  This defaults to all media types (view, audio and images)
58
- *  This can override customSmartCollections behavior (ie, remove video-only smart collections)
59
- */
60
-@property (nonatomic, strong) NSArray* mediaTypes;
61
-
62
-/**
63
- *  If set, it displays a this string instead of the localised default of "Done" on the done button. Note also that this
64
- *  is not used when a single selection is active since the selection of the chosen photo closes the VC thus rendering
65
- *  the button pointless.
66
- */
67
-@property (nonatomic) NSString* customDoneButtonTitle;
68
-
69
-/**
70
- *  If set, it displays this string instead of the localised default of "Cancel" on the cancel button
71
- */
72
-@property (nonatomic) NSString* customCancelButtonTitle;
73
-
74
-/**
75
- *  If set, it displays a prompt in the navigation bar
76
- */
77
-@property (nonatomic) NSString* customNavigationBarPrompt;
78
-
79
-/**
80
- *  Determines whether or not a toolbar with info about user selection is shown.
81
- *  The InfoToolbar is visible by default.
82
- */
83
-@property (nonatomic) BOOL displaySelectionInfoToolbar;
84
-
85
-/**
86
- *  Determines whether or not the number of assets is shown in the Album list.
87
- *  The number of assets is visible by default.
88
- */
89
-@property (nonatomic, assign) BOOL displayAlbumsNumberOfAssets;
90
-
91
-/**
92
- *  Automatically disables the "Done" button if nothing is selected. Defaults to YES.
93
- */
94
-@property (nonatomic, assign) BOOL autoDisableDoneButton;
95
-
96
-/**
97
- *  Use the picker either for miltiple image selections, or just a single selection. In the case of a single selection
98
- *  the VC is closed on selection so the Done button is neither displayed or used. Default is YES.
99
- */
100
-@property (nonatomic, assign) BOOL allowsMultipleSelection;
101
-
102
-/**
103
- * In the case where allowsMultipleSelection = NO, set this to YES to have the user confirm their selection. Default is NO.
104
- */
105
-@property (nonatomic, assign) BOOL confirmSingleSelection;
106
-
107
-/**
108
- * If set, it displays this string (if confirmSingleSelection = YES) instead of the localised default.
109
- */
110
-@property (nonatomic) NSString *confirmSingleSelectionPrompt;
111
-
112
-/**
113
- *  True to always show the toolbar, with a camera button allowing new photos to be taken. False to auto show/hide the
114
- *  toolbar, and have no camera button. Default is false. If true, this renders displaySelectionInfoToolbar a no-op.
115
- */
116
-@property (nonatomic, assign) BOOL showCameraButton;
117
-
118
-/**
119
- * True to auto select the image(s) taken with the camera if showCameraButton = YES. In the case of allowsMultipleSelection = YES,
120
- * this will trigger the selection handler too.
121
- */
122
-@property (nonatomic, assign) BOOL autoSelectCameraImages;
123
-
124
-/**
125
- * If set, the user is allowed to edit captured still images
126
- */
127
-@property (nonatomic, assign) BOOL allowsEditingCameraImages;
128
-
129
-/**
130
- *  Grid customizations:
131
- *
132
- *  - colsInPortrait: Number of columns in portrait (3 by default)
133
- *  - colsInLandscape: Number of columns in landscape (5 by default)
134
- *  - minimumInteritemSpacing: Horizontal and vertical minimum space between grid cells (2.0 by default)
135
- */
136
-@property (nonatomic) NSInteger colsInPortrait;
137
-@property (nonatomic) NSInteger colsInLandscape;
138
-@property (nonatomic) double minimumInteritemSpacing;
139
-
140
-/**
141
- * UI customizations:
142
- *
143
- * - pickerBackgroundColor: The colour for all backgrounds; behind the table and cells. Defaults to [UIColor whiteColor]
144
- * - pickerTextColor: The color for text in the views. This needs to work with pickerBackgroundColor! Default of darkTextColor
145
- * - toolbarBackgroundColor: The background color of the toolbar. Defaults to nil.
146
- * - toolbarBarTintColor: The color for the background tint of the toolbar. Defaults to nil.
147
- * - toolbarTextColor: The color of the text on the toolbar
148
- * - toolbarTintColor: The tint colour used for any buttons on the toolbar
149
- * - navigationBarBackgroundColor: The background of the navigation bar. Defaults to nil.
150
- * - navigationBarBarTintColor: The color for the background tint of the navigation bar. Defaults to nil.
151
- * - navigationBarTextColor: The color for the text in the navigation bar. Defaults to [UIColor darkTextColor]
152
- * - navigationBarTintColor: The tint color used for any buttons on the navigation Bar
153
- * - 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/
154
- * - 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.
155
- * - pickerFontNormalSize: The size of the custom font used in most places. Defaults to 14.0f
156
- * - pickerFontHeaderSize: The size of the custom font for album names. Defaults to 17.0f
157
- * - pickerStatusBarsStyle: On iPhones this will matter if custom navigation bar colours are being used. Defaults to UIStatusBarStyleDefault
158
- * - useCustomFontForNavigationBar: True to use the custom font (or it's default) in the navigation bar, false to leave to iOS Defaults.
159
- * - arrangeSmartCollectionsFirst: True will put the users smart collections above their albums, false will set it opposite. Default is NO.
160
- */
161
-@property (nonatomic, strong) UIColor *pickerBackgroundColor;
162
-@property (nonatomic, strong) UIColor *pickerTextColor;
163
-@property (nonatomic, strong) UIColor *toolbarBackgroundColor;
164
-@property (nonatomic, strong) UIColor *toolbarBarTintColor;
165
-@property (nonatomic, strong) UIColor *toolbarTextColor;
166
-@property (nonatomic, strong) UIColor *toolbarTintColor;
167
-@property (nonatomic, strong) UIColor *navigationBarBackgroundColor;
168
-@property (nonatomic, strong) UIColor *navigationBarBarTintColor;
169
-@property (nonatomic, strong) UIColor *navigationBarTextColor;
170
-@property (nonatomic, strong) UIColor *navigationBarTintColor;
171
-@property (nonatomic, strong) NSString *pickerFontName;
172
-@property (nonatomic, strong) NSString *pickerBoldFontName;
173
-@property (nonatomic) CGFloat pickerFontNormalSize;
174
-@property (nonatomic) CGFloat pickerFontHeaderSize;
175
-@property (nonatomic) UIStatusBarStyle pickerStatusBarStyle;
176
-@property (nonatomic) BOOL useCustomFontForNavigationBar;
177
-@property (nonatomic) BOOL arrangeSmartCollectionsFirst;
178
-
179
-/**
180
- * A reference to the navigation controller used to manage the whole picking process
181
- */
182
-@property (nonatomic, strong) UINavigationController *navigationController;
183
-
184
-/**
185
- *  Managing Asset Selection
186
- */
187
-- (void)selectAsset:(PHAsset *)asset;
188
-- (void)deselectAsset:(PHAsset *)asset;
189
-
190
-/**
191
- *  User finish Actions
192
- */
193
-- (void)dismiss:(id)sender;
194
-- (void)finishPickingAssets:(id)sender;
195
-
196
-@end
197
-
198
-
199
-
200
-@protocol GMImagePickerControllerDelegate <NSObject>
201
-
202
-/**
203
- *  @name Closing the Picker
204
- */
205
-
206
-/**
207
- *  Tells the delegate that the user finish picking photos or videos.
208
- *  @param picker The controller object managing the assets picker interface.
209
- *  @param assets An array containing picked PHAssets objects.
210
- */
211
-
212
-- (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets;
213
-
214
-
215
-@optional
216
-
217
-/**
218
- *  Tells the delegate that the user cancelled the pick operation.
219
- *  @param picker The controller object managing the assets picker interface.
220
- */
221
-- (void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker;
222
-
223
-
224
-/**
225
- *  @name Enabling Assets
226
- */
227
-
228
-/**
229
- *  Ask the delegate if the specified asset should be shown.
230
- *
231
- *  @param picker The controller object managing the assets picker interface.
232
- *  @param asset  The asset to be shown.
233
- *
234
- *  @return `YES` if the asset should be shown or `NO` if it should not.
235
- */
236
-
237
-- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset;
238
-
239
-/**
240
- *  Ask the delegate if the specified asset should be enabled for selection.
241
- *
242
- *  @param picker The controller object managing the assets picker interface.
243
- *  @param asset  The asset to be enabled.
244
- *
245
- *  @return `YES` if the asset should be enabled or `NO` if it should not.
246
- */
247
-- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldEnableAsset:(PHAsset *)asset;
248
-
249
-
250
-/**
251
- *  @name Managing the Selected Assets
252
- */
253
-
254
-/**
255
- *  Asks the delegate if the specified asset should be selected.
256
- *
257
- *  @param picker The controller object managing the assets picker interface.
258
- *  @param asset  The asset to be selected.
259
- *
260
- *  @return `YES` if the asset should be selected or `NO` if it should not.
261
- *
262
- */
263
-- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldSelectAsset:(PHAsset *)asset;
264
-
265
-/**
266
- *  Tells the delegate that the asset was selected.
267
- *
268
- *  @param picker   The controller object managing the assets picker interface.
269
- *  @param asset    The asset that was selected.
270
- *
271
- */
272
-- (void)assetsPickerController:(GMImagePickerController *)picker didSelectAsset:(PHAsset *)asset;
273
-
274
-/**
275
- *  Asks the delegate if the specified asset should be deselected.
276
- *
277
- *  @param picker The controller object managing the assets picker interface.
278
- *  @param asset  The asset to be deselected.
279
- *
280
- *  @return `YES` if the asset should be deselected or `NO` if it should not.
281
- *
282
- */
283
-- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldDeselectAsset:(PHAsset *)asset;
284
-
285
-/**
286
- *  Tells the delegate that the item at the specified path was deselected.
287
- *
288
- *  @param picker    The controller object managing the assets picker interface.
289
- *  @param asset The asset that was deselected.
290
- *
291
- */
292
-- (void)assetsPickerController:(GMImagePickerController *)picker didDeselectAsset:(PHAsset *)asset;
293
-
294
-
295
-
296
-/**
297
- *  @name Managing Asset Highlighting
298
- */
299
-
300
-/**
301
- *  Asks the delegate if the specified asset should be highlighted.
302
- *
303
- *  @param picker The controller object managing the assets picker interface.
304
- *  @param asset  The asset to be highlighted.
305
- *
306
- *  @return `YES` if the asset should be highlighted or `NO` if it should not.
307
- */
308
-- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldHighlightAsset:(PHAsset *)asset;
309
-
310
-/**
311
- *  Tells the delegate that asset was highlighted.
312
- *
313
- *  @param picker    The controller object managing the assets picker interface.
314
- *  @param asset The asset that was highlighted.
315
- *
316
- */
317
-- (void)assetsPickerController:(GMImagePickerController *)picker didHighlightAsset:(PHAsset *)asset;
318
-
319
-
320
-/**
321
- *  Tells the delegate that the highlight was removed from the asset.
322
- *
323
- *  @param picker    The controller object managing the assets picker interface.
324
- *  @param asset The asset that had its highlight removed.
325
- *
326
- */
327
-- (void)assetsPickerController:(GMImagePickerController *)picker didUnhighlightAsset:(PHAsset *)asset;
328
-
329
-
330
-
331
-
332
-@end

+ 0
- 388
GMImagePicker/GMImagePickerController.m View File

@@ -1,388 +0,0 @@
1
-//
2
-//  GMImagePickerController.m
3
-//  GMPhotoPicker
4
-//
5
-//  Created by Guillermo Muntaner Perelló on 19/09/14.
6
-//  Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
7
-//
8
-
9
-#import <MobileCoreServices/MobileCoreServices.h>
10
-#import "GMImagePickerController.h"
11
-#import "GMAlbumsViewController.h"
12
-#import <Photos/Photos.h>
13
-
14
-@interface GMImagePickerController () <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIAlertViewDelegate>
15
-
16
-@end
17
-
18
-@implementation GMImagePickerController
19
-
20
-- (id)init
21
-{
22
-    if (self = [super init]) {
23
-        _selectedAssets = [[NSMutableArray alloc] init];
24
-        
25
-        // Default values:
26
-        _displaySelectionInfoToolbar = YES;
27
-        _displayAlbumsNumberOfAssets = YES;
28
-        _autoDisableDoneButton = YES;
29
-        _allowsMultipleSelection = YES;
30
-        _confirmSingleSelection = NO;
31
-        _showCameraButton = NO;
32
-        
33
-        // Grid configuration:
34
-        _colsInPortrait = 3;
35
-        _colsInLandscape = 5;
36
-        _minimumInteritemSpacing = 2.0;
37
-        
38
-        // Sample of how to select the collections you want to display:
39
-        _customSmartCollections = @[@(PHAssetCollectionSubtypeSmartAlbumFavorites),
40
-                                    @(PHAssetCollectionSubtypeSmartAlbumRecentlyAdded),
41
-                                    @(PHAssetCollectionSubtypeSmartAlbumVideos),
42
-                                    @(PHAssetCollectionSubtypeSmartAlbumSlomoVideos),
43
-                                    @(PHAssetCollectionSubtypeSmartAlbumTimelapses),
44
-                                    @(PHAssetCollectionSubtypeSmartAlbumBursts),
45
-                                    @(PHAssetCollectionSubtypeSmartAlbumPanoramas)];
46
-        // If you don't want to show smart collections, just put _customSmartCollections to nil;
47
-        //_customSmartCollections=nil;
48
-        
49
-        // Which media types will display
50
-        _mediaTypes = @[@(PHAssetMediaTypeAudio),
51
-                        @(PHAssetMediaTypeVideo),
52
-                        @(PHAssetMediaTypeImage)];
53
-        
54
-        self.preferredContentSize = kPopoverContentSize;
55
-        
56
-        // UI Customisation
57
-        _pickerBackgroundColor = [UIColor whiteColor];
58
-        _pickerTextColor = [UIColor darkTextColor];
59
-        _pickerFontName = @"HelveticaNeue";
60
-        _pickerBoldFontName = @"HelveticaNeue-Bold";
61
-        _pickerFontNormalSize = 14.0f;
62
-        _pickerFontHeaderSize = 17.0f;
63
-        
64
-        _navigationBarBackgroundColor = [UIColor whiteColor];
65
-        _navigationBarTextColor = [UIColor darkTextColor];
66
-        _navigationBarTintColor = [UIColor darkTextColor];
67
-        
68
-        _toolbarBarTintColor = [UIColor whiteColor];
69
-        _toolbarTextColor = [UIColor darkTextColor];
70
-        _toolbarTintColor = [UIColor darkTextColor];
71
-        
72
-        _pickerStatusBarStyle = UIStatusBarStyleDefault;
73
-        
74
-        [self setupNavigationController];
75
-    }
76
-    return self;
77
-}
78
-
79
-- (void)viewWillAppear:(BOOL)animated
80
-{
81
-    [super viewWillAppear:animated];
82
-
83
-    // Ensure nav and toolbar customisations are set. Defaults are in place, but the user may have changed them
84
-    self.view.backgroundColor = _pickerBackgroundColor;
85
-
86
-    _navigationController.toolbar.translucent = YES;
87
-    _navigationController.toolbar.barTintColor = _toolbarBarTintColor;
88
-    _navigationController.toolbar.tintColor = _toolbarTintColor;
89
-    
90
-    _navigationController.navigationBar.backgroundColor = _navigationBarBackgroundColor;
91
-    _navigationController.navigationBar.tintColor = _navigationBarTintColor;
92
-    NSDictionary *attributes;
93
-    if (_useCustomFontForNavigationBar) {
94
-        attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor,
95
-                       NSFontAttributeName : [UIFont fontWithName:_pickerBoldFontName size:_pickerFontHeaderSize]};
96
-    } else {
97
-        attributes = @{NSForegroundColorAttributeName : _navigationBarTextColor};
98
-    }
99
-    _navigationController.navigationBar.titleTextAttributes = attributes;
100
-    
101
-    [self updateToolbar];
102
-}
103
-
104
-- (UIStatusBarStyle)preferredStatusBarStyle {
105
-    return _pickerStatusBarStyle;
106
-}
107
-
108
-
109
-#pragma mark - Setup Navigation Controller
110
-
111
-- (void)setupNavigationController
112
-{
113
-    GMAlbumsViewController *albumsViewController = [[GMAlbumsViewController alloc] init];
114
-    _navigationController = [[UINavigationController alloc] initWithRootViewController:albumsViewController];
115
-    _navigationController.delegate = self;
116
-    [_navigationController.navigationBar setTranslucent:NO];
117
-    [_navigationController willMoveToParentViewController:self];
118
-    [_navigationController.view setFrame:self.view.frame];
119
-    [self.view addSubview:_navigationController.view];
120
-    [self addConstraintsToChildViewControllersView:_navigationController.view];
121
-    [self addChildViewController:_navigationController];
122
-    [_navigationController didMoveToParentViewController:self];
123
-}
124
-
125
-- (void)addConstraintsToChildViewControllersView:(UIView *)view {
126
-    view.translatesAutoresizingMaskIntoConstraints = NO;
127
-    NSArray * hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|" options:0 metrics:0 views:NSDictionaryOfVariableBindings(view)];
128
-    NSLayoutConstraint * topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
129
-    NSLayoutConstraint * bottomConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
130
-    [view.superview addConstraints:@[topConstraint,bottomConstraint]];
131
-    [view.superview addConstraints:hConstraints];
132
-}
133
-
134
-
135
-#pragma mark - UIAlertViewDelegate
136
-
137
--(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
138
-{
139
-    if (buttonIndex == 1) {
140
-        // Only if OK was pressed do we want to completge the selection
141
-        [self finishPickingAssets:self];
142
-    }
143
-}
144
-
145
-
146
-#pragma mark - Select / Deselect Asset
147
-
148
-- (void)selectAsset:(PHAsset *)asset
149
-{
150
-    [self.selectedAssets insertObject:asset atIndex:self.selectedAssets.count];
151
-    [self updateDoneButton];
152
-    
153
-    if (!self.allowsMultipleSelection) {
154
-        if (self.confirmSingleSelection) {
155
-            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?")];
156
-            
157
-            [[[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.confirm.title",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"Are You Sure?")]
158
-                                        message:message
159
-                                       delegate:self
160
-                              cancelButtonTitle:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.no",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"No")]
161
-                              otherButtonTitles:[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.action.yes",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"Yes")], nil] show];
162
-        } else {
163
-            [self finishPickingAssets:self];
164
-        }
165
-    } else if (self.displaySelectionInfoToolbar || self.showCameraButton) {
166
-        [self updateToolbar];
167
-    }
168
-}
169
-
170
-- (void)deselectAsset:(PHAsset *)asset
171
-{
172
-    [self.selectedAssets removeObjectAtIndex:[self.selectedAssets indexOfObject:asset]];
173
-    if (self.selectedAssets.count == 0) {
174
-        [self updateDoneButton];
175
-    }
176
-    
177
-    if (self.displaySelectionInfoToolbar || self.showCameraButton) {
178
-        [self updateToolbar];
179
-    }
180
-}
181
-
182
-- (void)updateDoneButton
183
-{
184
-    if (!self.allowsMultipleSelection) {
185
-        return;
186
-    }
187
-    
188
-    UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
189
-    for (UIViewController *viewController in nav.viewControllers) {
190
-        viewController.navigationItem.rightBarButtonItem.enabled = (self.autoDisableDoneButton ? self.selectedAssets.count > 0 : TRUE);
191
-    }
192
-}
193
-
194
-- (void)updateToolbar
195
-{
196
-    if (!self.allowsMultipleSelection && !self.showCameraButton) {
197
-        return;
198
-    }
199
-
200
-    UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
201
-    for (UIViewController *viewController in nav.viewControllers) {
202
-        NSUInteger index = 1;
203
-        if (_showCameraButton) {
204
-            index++;
205
-        }
206
-        [[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateNormal];
207
-        [[viewController.toolbarItems objectAtIndex:index] setTitleTextAttributes:[self toolbarTitleTextAttributes] forState:UIControlStateDisabled];
208
-        [[viewController.toolbarItems objectAtIndex:index] setTitle:[self toolbarTitle]];
209
-        [viewController.navigationController setToolbarHidden:(self.selectedAssets.count == 0 && !self.showCameraButton) animated:YES];
210
-    }
211
-}
212
-
213
-
214
-#pragma mark - User finish Actions
215
-
216
-- (void)dismiss:(id)sender
217
-{
218
-    if ([self.delegate respondsToSelector:@selector(assetsPickerControllerDidCancel:)]) {
219
-        [self.delegate assetsPickerControllerDidCancel:self];
220
-    }
221
-    
222
-    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
223
-}
224
-
225
-
226
-- (void)finishPickingAssets:(id)sender
227
-{
228
-    if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingAssets:)]) {
229
-        [self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedAssets];
230
-    }
231
-}
232
-
233
-
234
-#pragma mark - Toolbar Title
235
-
236
-- (NSPredicate *)predicateOfAssetType:(PHAssetMediaType)type
237
-{
238
-    return [NSPredicate predicateWithBlock:^BOOL(PHAsset *asset, NSDictionary *bindings) {
239
-        return (asset.mediaType == type);
240
-    }];
241
-}
242
-
243
-- (NSString *)toolbarTitle
244
-{
245
-    if (self.selectedAssets.count == 0) {
246
-        return nil;
247
-    }
248
-    
249
-    NSPredicate *photoPredicate = [self predicateOfAssetType:PHAssetMediaTypeImage];
250
-    NSPredicate *videoPredicate = [self predicateOfAssetType:PHAssetMediaTypeVideo];
251
-    
252
-    NSInteger nImages = [self.selectedAssets filteredArrayUsingPredicate:photoPredicate].count;
253
-    NSInteger nVideos = [self.selectedAssets filteredArrayUsingPredicate:videoPredicate].count;
254
-    
255
-    if (nImages > 0 && nVideos > 0) {
256
-        return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-items",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"%@ Items Selected" ), @(nImages + nVideos)];
257
-    } else if (nImages > 1) {
258
-        return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-photos",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"%@ Photos Selected"), @(nImages)];
259
-    } else if (nImages == 1) {
260
-        return NSLocalizedStringFromTableInBundle(@"picker.selection.single-photo",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"1 Photo Selected" );
261
-    } else if (nVideos > 1) {
262
-        return [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"picker.selection.multiple-videos",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"%@ Videos Selected"), @(nVideos)];
263
-    } else if (nVideos == 1) {
264
-        return NSLocalizedStringFromTableInBundle(@"picker.selection.single-video",  @"GMImagePicker", [NSBundle bundleForClass:GMImagePickerController.class],  @"1 Video Selected");
265
-    } else {
266
-        return nil;
267
-    }
268
-}
269
-
270
-
271
-#pragma mark - Toolbar Items
272
-
273
-- (void)cameraButtonPressed:(UIBarButtonItem *)button
274
-{
275
-    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
276
-        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Camera!"
277
-                                                        message:@"Sorry, this device does not have a camera."
278
-                                                       delegate:nil
279
-                                              cancelButtonTitle:@"OK"
280
-                                              otherButtonTitles:nil];
281
-        [alert show];
282
-
283
-        return;
284
-    }
285
-    
286
-    // This allows the selection of the image taken to be better seen if the user is not already in that VC
287
-    if (self.autoSelectCameraImages && [self.navigationController.topViewController isKindOfClass:[GMAlbumsViewController class]]) {
288
-        [((GMAlbumsViewController *)self.navigationController.topViewController) selectAllAlbumsCell];
289
-    }
290
-    
291
-    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
292
-    picker.sourceType = UIImagePickerControllerSourceTypeCamera;
293
-    picker.mediaTypes = @[(NSString *)kUTTypeImage];
294
-    picker.allowsEditing = self.allowsEditingCameraImages;
295
-    picker.delegate = self;
296
-    picker.modalPresentationStyle = UIModalPresentationPopover;
297
-    
298
-    UIPopoverPresentationController *popPC = picker.popoverPresentationController;
299
-    popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
300
-    popPC.barButtonItem = button;
301
-    
302
-    [self showViewController:picker sender:button];
303
-}
304
-
305
-- (NSDictionary *)toolbarTitleTextAttributes {
306
-    return @{NSForegroundColorAttributeName : _toolbarTextColor,
307
-             NSFontAttributeName : [UIFont fontWithName:_pickerFontName size:_pickerFontHeaderSize]};
308
-}
309
-
310
-- (UIBarButtonItem *)titleButtonItem
311
-{
312
-    UIBarButtonItem *title = [[UIBarButtonItem alloc] initWithTitle:self.toolbarTitle
313
-                                                              style:UIBarButtonItemStylePlain
314
-                                                             target:nil
315
-                                                             action:nil];
316
-    
317
-    NSDictionary *attributes = [self toolbarTitleTextAttributes];
318
-    [title setTitleTextAttributes:attributes forState:UIControlStateNormal];
319
-    [title setTitleTextAttributes:attributes forState:UIControlStateDisabled];
320
-    [title setEnabled:NO];
321
-    
322
-    return title;
323
-}
324
-
325
-- (UIBarButtonItem *)spaceButtonItem
326
-{
327
-    return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
328
-}
329
-
330
-- (UIBarButtonItem *)cameraButtonItem
331
-{
332
-    return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@selector(cameraButtonPressed:)];
333
-}
334
-
335
-- (NSArray *)toolbarItems
336
-{
337
-    UIBarButtonItem *camera = [self cameraButtonItem];
338
-    UIBarButtonItem *title  = [self titleButtonItem];
339
-    UIBarButtonItem *space  = [self spaceButtonItem];
340
-    
341
-    NSMutableArray *items = [[NSMutableArray alloc] init];
342
-    if (_showCameraButton) {
343
-        [items addObject:camera];
344
-    }
345
-    [items addObject:space];
346
-    [items addObject:title];
347
-    [items addObject:space];
348
-    
349
-    return [NSArray arrayWithArray:items];
350
-}
351
-
352
-
353
-#pragma mark - Camera Delegate
354
-
355
-- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
356
-{
357
-    [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
358
-
359
-    NSString *mediaType = info[UIImagePickerControllerMediaType];
360
-    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
361
-        UIImage *image = info[UIImagePickerControllerEditedImage] ? : info[UIImagePickerControllerOriginalImage];
362
-        UIImageWriteToSavedPhotosAlbum(image,
363
-                                       self,
364
-                                       @selector(image:finishedSavingWithError:contextInfo:),
365
-                                       nil);
366
-    }
367
-}
368
-
369
--(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
370
-{
371
-    [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
372
-}
373
-
374
--(void)image:(UIImage *)image finishedSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
375
-{
376
-    if (error) {
377
-        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Image Not Saved"
378
-                                                        message:@"Sorry, unable to save the new image!"
379
-                                                       delegate:nil
380
-                                              cancelButtonTitle:@"OK"
381
-                                              otherButtonTitles:nil];
382
-        [alert show];
383
-    }
384
-    
385
-    // Note: The image view will auto refresh as the photo's are being observed in the other VCs
386
-}
387
-
388
-@end

BIN
GMImagePicker/GMSelected.png View File


BIN
GMImagePicker/GMSelected@2x.png View File


BIN
GMImagePicker/GMVideoIcon.png View File


BIN
GMImagePicker/GMVideoIcon@2x.png View File


+ 0
- 22
GMImagePicker/Info.plist View File

@@ -1,22 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8"?>
2
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
-<plist version="1.0">
4
-<dict>
5
-	<key>CFBundleDevelopmentRegion</key>
6
-	<string>$(DEVELOPMENT_LANGUAGE)</string>
7
-	<key>CFBundleExecutable</key>
8
-	<string>$(EXECUTABLE_NAME)</string>
9
-	<key>CFBundleIdentifier</key>
10
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
-	<key>CFBundleInfoDictionaryVersion</key>
12
-	<string>6.0</string>
13
-	<key>CFBundleName</key>
14
-	<string>$(PRODUCT_NAME)</string>
15
-	<key>CFBundlePackageType</key>
16
-	<string>FMWK</string>
17
-	<key>CFBundleShortVersionString</key>
18
-	<string>1.0</string>
19
-	<key>CFBundleVersion</key>
20
-	<string>$(CURRENT_PROJECT_VERSION)</string>
21
-</dict>
22
-</plist>

BIN
GMImagePicker/ca.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/de.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/en.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/es.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/fr.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/it.lproj/GMImagePicker.strings View File


BIN
GMImagePicker/pt.lproj/GMImagePicker.strings View File


+ 98
- 256
Tusker.xcodeproj/project.pbxproj View File

@@ -3,7 +3,7 @@
3 3
 	archiveVersion = 1;
4 4
 	classes = {
5 5
 	};
6
-	objectVersion = 50;
6
+	objectVersion = 52;
7 7
 	objects = {
8 8
 
9 9
 /* Begin PBXBuildFile section */
@@ -25,9 +25,6 @@
25 25
 		04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
26 26
 		04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
27 27
 		D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
28
-		D60A548F21ED515800F1F87C /* GMImagePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = D60A548D21ED515800F1F87C /* GMImagePicker.h */; settings = {ATTRIBUTES = (Public, ); }; };
29
-		D60A549221ED515800F1F87C /* GMImagePicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; };
30
-		D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D60A548B21ED515800F1F87C /* GMImagePicker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31 28
 		D60C07E421E8176B0057FAA8 /* ComposeMediaView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */; };
32 29
 		D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
33 30
 		D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
@@ -76,6 +73,13 @@
76 73
 		D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
77 74
 		D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
78 75
 		D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
76
+		D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
77
+		D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; };
78
+		D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
79
+		D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; };
80
+		D626493C23C1000300612E6E /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493A23C1000300612E6E /* AlbumTableViewCell.swift */; };
81
+		D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493B23C1000300612E6E /* AlbumTableViewCell.xib */; };
82
+		D626493F23C101C500612E6E /* AlbumAssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */; };
79 83
 		D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
80 84
 		D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; };
81 85
 		D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; };
@@ -109,6 +113,7 @@
109 113
 		D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
110 114
 		D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
111 115
 		D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
116
+		D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
112 117
 		D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
113 118
 		D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
114 119
 		D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
@@ -142,30 +147,6 @@
142 147
 		D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */; };
143 148
 		D67C57B221E28FAD00C3118B /* ComposeStatusReplyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */; };
144 149
 		D67C57B421E2910700C3118B /* ComposeStatusReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */; };
145
-		D686329521ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686326E21ED8312008C716E /* GMImagePicker.strings */; };
146
-		D686329621ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327121ED8312008C716E /* GMImagePicker.strings */; };
147
-		D686329721ED8319008C716E /* GMGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327321ED8312008C716E /* GMGridViewCell.m */; };
148
-		D686329821ED8319008C716E /* GMVideoIcon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327421ED8312008C716E /* GMVideoIcon@2x.png */; };
149
-		D686329921ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327621ED8313008C716E /* GMImagePicker.strings */; };
150
-		D686329A21ED8319008C716E /* GMAlbumsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686327821ED8313008C716E /* GMAlbumsViewController.m */; };
151
-		D686329B21ED8319008C716E /* GMVideoIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327921ED8313008C716E /* GMVideoIcon.png */; };
152
-		D686329C21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686327B21ED8313008C716E /* GMImagePicker.strings */; };
153
-		D686329D21ED8319008C716E /* GMEmptyFolder@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */; };
154
-		D686329E21ED8319008C716E /* GMAlbumsViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327E21ED8314008C716E /* GMAlbumsViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
155
-		D686329F21ED8319008C716E /* GMGridViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686327F21ED8315008C716E /* GMGridViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
156
-		D68632A021ED8319008C716E /* GMSelected.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328021ED8315008C716E /* GMSelected.png */; };
157
-		D68632A121ED8319008C716E /* GMAlbumsViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328121ED8315008C716E /* GMAlbumsViewCell.m */; };
158
-		D68632A221ED8319008C716E /* GMAlbumsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328221ED8316008C716E /* GMAlbumsViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
159
-		D68632A321ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328421ED8316008C716E /* GMImagePicker.strings */; };
160
-		D68632A421ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328721ED8317008C716E /* GMImagePicker.strings */; };
161
-		D68632A521ED8319008C716E /* GMSelected@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328921ED8317008C716E /* GMSelected@2x.png */; };
162
-		D68632A621ED8319008C716E /* GMImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686328A21ED8317008C716E /* GMImagePickerController.m */; };
163
-		D68632A721ED8319008C716E /* GMGridViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = D686328B21ED8317008C716E /* GMGridViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
164
-		D68632A821ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686328D21ED8317008C716E /* GMImagePicker.strings */; };
165
-		D68632A921ED8319008C716E /* GMEmptyFolder@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */; };
166
-		D68632AA21ED8319008C716E /* GMGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D686329021ED8319008C716E /* GMGridViewController.m */; };
167
-		D68632AB21ED8319008C716E /* GMImagePickerController.h in Headers */ = {isa = PBXBuildFile; fileRef = D686329121ED8319008C716E /* GMImagePickerController.h */; settings = {ATTRIBUTES = (Public, ); }; };
168
-		D68632AC21ED8319008C716E /* GMImagePicker.strings in Resources */ = {isa = PBXBuildFile; fileRef = D686329321ED8319008C716E /* GMImagePicker.strings */; };
169 150
 		D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68FEC4E232C5BC300C84F23 /* SegmentedPageViewController.swift */; };
170 151
 		D6945C2F23AC47C3005C403C /* SavedHashtagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C2E23AC47C3005C403C /* SavedHashtagsManager.swift */; };
171 152
 		D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3123AC4D36005C403C /* HashtagTimelineViewController.swift */; };
@@ -194,6 +175,13 @@
194 175
 		D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4423216AF800E5038B /* FollowAccountActivity.swift */; };
195 176
 		D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4723216B1D00E5038B /* AccountActivity.swift */; };
196 177
 		D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB4923216F0400E5038B /* UnfollowAccountActivity.swift */; };
178
+		D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */ = {isa = PBXBuildFile; productRef = D6B0539E23BD2BA300A066FA /* SheetController */; };
179
+		D6B053A223BD2C0600A066FA /* AssetPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A123BD2C0600A066FA /* AssetPickerViewController.swift */; };
180
+		D6B053A423BD2C8100A066FA /* AssetCollectionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A323BD2C8100A066FA /* AssetCollectionsListViewController.swift */; };
181
+		D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A523BD2D0C00A066FA /* AssetCollectionViewController.swift */; };
182
+		D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */; };
183
+		D6B053AC23BD2F1400A066FA /* AssetCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */; };
184
+		D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B053AD23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift */; };
197 185
 		D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
198 186
 		D6BC874521961F73006163F1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; };
199 187
 		D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BC874421961F73006163F1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -229,13 +217,6 @@
229 217
 /* End PBXBuildFile section */
230 218
 
231 219
 /* Begin PBXContainerItemProxy section */
232
-		D60A549021ED515800F1F87C /* PBXContainerItemProxy */ = {
233
-			isa = PBXContainerItemProxy;
234
-			containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
235
-			proxyType = 1;
236
-			remoteGlobalIDString = D60A548A21ED515800F1F87C;
237
-			remoteInfo = GMImagePicker;
238
-		};
239 220
 		D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
240 221
 			isa = PBXContainerItemProxy;
241 222
 			containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
@@ -280,7 +261,6 @@
280 261
 			dstPath = "";
281 262
 			dstSubfolderSpec = 10;
282 263
 			files = (
283
-				D60A549321ED515800F1F87C /* GMImagePicker.framework in Embed Frameworks */,
284 264
 				D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
285 265
 				D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
286 266
 				D6BC874621961F73006163F1 /* Gifu.framework in Embed Frameworks */,
@@ -311,8 +291,6 @@
311 291
 		D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
312 292
 		D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
313 293
 		D60A548B21ED515800F1F87C /* GMImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GMImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
314
-		D60A548D21ED515800F1F87C /* GMImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GMImagePicker.h; sourceTree = "<group>"; };
315
-		D60A548E21ED515800F1F87C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
316 294
 		D60C07E321E8176B0057FAA8 /* ComposeMediaView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeMediaView.xib; sourceTree = "<group>"; };
317 295
 		D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
318 296
 		D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -362,6 +340,13 @@
362 340
 		D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
363 341
 		D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
364 342
 		D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
343
+		D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
344
+		D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
345
+		D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
346
+		D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
347
+		D626493A23C1000300612E6E /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = "<group>"; };
348
+		D626493B23C1000300612E6E /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = "<group>"; };
349
+		D626493E23C101C500612E6E /* AlbumAssetCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumAssetCollectionViewController.swift; sourceTree = "<group>"; };
365 350
 		D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
366 351
 		D627943423A5525100D38C68 /* StatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivity.swift; sourceTree = "<group>"; };
367 352
 		D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
@@ -394,6 +379,7 @@
394 379
 		D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
395 380
 		D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
396 381
 		D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
382
+		D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
397 383
 		D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
398 384
 		D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
399 385
 		D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
@@ -428,30 +414,6 @@
428 414
 		D67C57AE21E28EAD00C3118B /* Array+Uniques.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Uniques.swift"; sourceTree = "<group>"; };
429 415
 		D67C57B121E28FAD00C3118B /* ComposeStatusReplyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ComposeStatusReplyView.xib; sourceTree = "<group>"; };
430 416
 		D67C57B321E2910700C3118B /* ComposeStatusReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusReplyView.swift; sourceTree = "<group>"; };
431
-		D686326F21ED8312008C716E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = GMImagePicker.strings; sourceTree = "<group>"; };
432
-		D686327221ED8312008C716E /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = GMImagePicker.strings; sourceTree = "<group>"; };
433
-		D686327321ED8312008C716E /* GMGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMGridViewCell.m; sourceTree = "<group>"; };
434
-		D686327421ED8312008C716E /* GMVideoIcon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMVideoIcon@2x.png"; sourceTree = "<group>"; };
435
-		D686327721ED8313008C716E /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = GMImagePicker.strings; sourceTree = "<group>"; };
436
-		D686327821ED8313008C716E /* GMAlbumsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewController.m; sourceTree = "<group>"; };
437
-		D686327921ED8313008C716E /* GMVideoIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMVideoIcon.png; sourceTree = "<group>"; };
438
-		D686327C21ED8313008C716E /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = GMImagePicker.strings; sourceTree = "<group>"; };
439
-		D686327D21ED8314008C716E /* GMEmptyFolder@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@2x.png"; sourceTree = "<group>"; };
440
-		D686327E21ED8314008C716E /* GMAlbumsViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewCell.h; sourceTree = "<group>"; };
441
-		D686327F21ED8315008C716E /* GMGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewController.h; sourceTree = "<group>"; };
442
-		D686328021ED8315008C716E /* GMSelected.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GMSelected.png; sourceTree = "<group>"; };
443
-		D686328121ED8315008C716E /* GMAlbumsViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMAlbumsViewCell.m; sourceTree = "<group>"; };
444
-		D686328221ED8316008C716E /* GMAlbumsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMAlbumsViewController.h; sourceTree = "<group>"; };
445
-		D686328521ED8316008C716E /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = GMImagePicker.strings; sourceTree = "<group>"; };
446
-		D686328821ED8317008C716E /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = GMImagePicker.strings; sourceTree = "<group>"; };
447
-		D686328921ED8317008C716E /* GMSelected@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMSelected@2x.png"; sourceTree = "<group>"; };
448
-		D686328A21ED8317008C716E /* GMImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GMImagePickerController.m; sourceTree = "<group>"; };
449
-		D686328B21ED8317008C716E /* GMGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GMGridViewCell.h; sourceTree = "<group>"; };
450
-		D686328E21ED8317008C716E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = GMImagePicker.strings; sourceTree = "<group>"; };
451
-		D686328F21ED8318008C716E /* GMEmptyFolder@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GMEmptyFolder@1x.png"; sourceTree = "<group>"; };
452
-		D686329021ED8319008C716E /* GMGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKn