UGCKitAlbumsViewController.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. //Copyright (c) 2015 Katsuma Tanaka
  2. //Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to
  3. //deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  4. //sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  5. //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  6. //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  7. //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  8. //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  9. //IN THE SOFTWARE.
  10. #import "UGCKitAlbumsViewController.h"
  11. #import <Photos/Photos.h>
  12. // Views
  13. #import "UGCKitAlbumCell.h"
  14. // ViewControllers
  15. #import "UGCKitMediaPickerViewControllerPrivate.h"
  16. #import "UGCKitAssetsViewController.h"
  17. static CGSize CGSizeScale(CGSize size, CGFloat scale) {
  18. return CGSizeMake(size.width * scale, size.height * scale);
  19. }
  20. @interface UGCKitMediaPickerViewController (Private)
  21. @property (nonatomic, strong) NSBundle *assetBundle;
  22. @end
  23. @interface UGCKitAlbumsViewController () <PHPhotoLibraryChangeObserver>
  24. @property (nonatomic, strong) IBOutlet UIBarButtonItem *doneButton;
  25. @property (nonatomic, copy) NSArray *fetchResults;
  26. @end
  27. @implementation UGCKitAlbumsViewController
  28. - (void)viewDidLoad
  29. {
  30. [super viewDidLoad];
  31. [self setUpToolbarItems];
  32. /*
  33. // Fetch user albums and smart albums
  34. __weak __typeof(self) weakSelf = self;
  35. void (^doFetch)(void) = ^{
  36. __strong __typeof(weakSelf) self = weakSelf;
  37. PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
  38. PHFetchResult *userAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
  39. self.fetchResults = @[smartAlbums, userAlbums];
  40. [self updateAssetCollections];
  41. [self.tableView reloadData];
  42. };
  43. // Register observer
  44. [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
  45. if (PHAuthorizationStatusAuthorized != [PHPhotoLibrary authorizationStatus]) {
  46. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  47. if (PHAuthorizationStatusAuthorized == status) {
  48. dispatch_async(dispatch_get_main_queue(), doFetch);
  49. }
  50. }];
  51. } else {
  52. doFetch();
  53. }
  54. */
  55. }
  56. - (void)viewWillAppear:(BOOL)animated
  57. {
  58. [super viewWillAppear:animated];
  59. // Configure navigation item
  60. self.navigationItem.title = [_theme localizedString:@"UGCKit.MediaPicker.Title"];
  61. self.navigationItem.prompt = self.imagePickerController.config.prompt;
  62. // Show/hide 'Done' button
  63. if (self.imagePickerController.allowsMultipleSelection) {
  64. [self.navigationItem setRightBarButtonItem:self.doneButton animated:NO];
  65. } else {
  66. [self.navigationItem setRightBarButtonItem:nil animated:NO];
  67. }
  68. [self updateControlState];
  69. [self updateSelectionInfo];
  70. }
  71. - (void)dealloc
  72. {
  73. // Deregister observer
  74. [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
  75. }
  76. #pragma mark - Storyboard
  77. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  78. {
  79. UGCKitAssetsViewController *assetsViewController = segue.destinationViewController;
  80. assetsViewController.theme = _theme;
  81. assetsViewController.imagePickerController = self.imagePickerController;
  82. assetsViewController.assetCollection = self.assetCollections[self.tableView.indexPathForSelectedRow.row];
  83. }
  84. #pragma mark - Actions
  85. - (IBAction)cancel:(id)sender
  86. {
  87. [self dismissViewControllerAnimated:YES completion:nil];
  88. }
  89. - (IBAction)done:(id)sender
  90. {
  91. }
  92. #pragma mark - Toolbar
  93. - (void)setUpToolbarItems
  94. {
  95. // Space
  96. UIBarButtonItem *leftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL];
  97. UIBarButtonItem *rightSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL];
  98. // Info label
  99. NSDictionary *attributes = @{ NSForegroundColorAttributeName: [UIColor blackColor] };
  100. UIBarButtonItem *infoButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:NULL];
  101. infoButtonItem.enabled = NO;
  102. [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateNormal];
  103. [infoButtonItem setTitleTextAttributes:attributes forState:UIControlStateDisabled];
  104. self.toolbarItems = @[leftSpace, infoButtonItem, rightSpace];
  105. }
  106. - (void)updateSelectionInfo
  107. {
  108. NSMutableOrderedSet *selectedAssets = self.imagePickerController.selectedAssets;
  109. if (selectedAssets.count > 0) {
  110. NSString *format;
  111. if (selectedAssets.count > 1) {
  112. format = [_theme localizedString:@"UGCKit.MediaPicker.Toolbar.ItemsSelected"];
  113. } else {
  114. format = [_theme localizedString:@"UGCKit.MediaPicker.Toolbar.ItemSelected"];
  115. }
  116. NSString *title = [NSString stringWithFormat:format, selectedAssets.count];
  117. [(UIBarButtonItem *)self.toolbarItems[1] setTitle:title];
  118. } else {
  119. [(UIBarButtonItem *)self.toolbarItems[1] setTitle:@""];
  120. }
  121. }
  122. #pragma mark - Fetching Asset Collections
  123. - (void)updateAssetCollections
  124. {
  125. // Filter albums
  126. NSArray *assetCollectionSubtypes = self.imagePickerController.assetCollectionSubtypes;
  127. NSMutableDictionary *smartAlbums = [NSMutableDictionary dictionaryWithCapacity:assetCollectionSubtypes.count];
  128. NSMutableArray *userAlbums = [NSMutableArray array];
  129. for (PHFetchResult *fetchResult in self.fetchResults) {
  130. [fetchResult enumerateObjectsUsingBlock:^(PHAssetCollection *assetCollection, NSUInteger index, BOOL *stop) {
  131. PHAssetCollectionSubtype subtype = assetCollection.assetCollectionSubtype;
  132. if (subtype == PHAssetCollectionSubtypeAlbumRegular) {
  133. [userAlbums addObject:assetCollection];
  134. } else if ([assetCollectionSubtypes containsObject:@(subtype)]) {
  135. if (!smartAlbums[@(subtype)]) {
  136. smartAlbums[@(subtype)] = [NSMutableArray array];
  137. }
  138. [smartAlbums[@(subtype)] addObject:assetCollection];
  139. }
  140. }];
  141. }
  142. NSMutableArray *assetCollections = [NSMutableArray array];
  143. // Fetch smart albums
  144. for (NSNumber *assetCollectionSubtype in assetCollectionSubtypes) {
  145. NSArray *collections = smartAlbums[assetCollectionSubtype];
  146. if (collections) {
  147. [assetCollections addObjectsFromArray:collections];
  148. }
  149. }
  150. // Fetch user albums
  151. [userAlbums enumerateObjectsUsingBlock:^(PHAssetCollection *assetCollection, NSUInteger index, BOOL *stop) {
  152. [assetCollections addObject:assetCollection];
  153. }];
  154. self.assetCollections = assetCollections;
  155. }
  156. - (UIImage *)placeholderImageWithSize:(CGSize)size
  157. {
  158. UIGraphicsBeginImageContext(size);
  159. CGContextRef context = UIGraphicsGetCurrentContext();
  160. UIColor *backgroundColor = [UIColor colorWithRed:(239.0 / 255.0) green:(239.0 / 255.0) blue:(244.0 / 255.0) alpha:1.0];
  161. UIColor *iconColor = [UIColor colorWithRed:(179.0 / 255.0) green:(179.0 / 255.0) blue:(182.0 / 255.0) alpha:1.0];
  162. // Background
  163. CGContextSetFillColorWithColor(context, [backgroundColor CGColor]);
  164. CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
  165. // Icon (back)
  166. CGRect backIconRect = CGRectMake(size.width * (16.0 / 68.0),
  167. size.height * (20.0 / 68.0),
  168. size.width * (32.0 / 68.0),
  169. size.height * (24.0 / 68.0));
  170. CGContextSetFillColorWithColor(context, [iconColor CGColor]);
  171. CGContextFillRect(context, backIconRect);
  172. CGContextSetFillColorWithColor(context, [backgroundColor CGColor]);
  173. CGContextFillRect(context, CGRectInset(backIconRect, 1.0, 1.0));
  174. // Icon (front)
  175. CGRect frontIconRect = CGRectMake(size.width * (20.0 / 68.0),
  176. size.height * (24.0 / 68.0),
  177. size.width * (32.0 / 68.0),
  178. size.height * (24.0 / 68.0));
  179. CGContextSetFillColorWithColor(context, [backgroundColor CGColor]);
  180. CGContextFillRect(context, CGRectInset(frontIconRect, -1.0, -1.0));
  181. CGContextSetFillColorWithColor(context, [iconColor CGColor]);
  182. CGContextFillRect(context, frontIconRect);
  183. CGContextSetFillColorWithColor(context, [backgroundColor CGColor]);
  184. CGContextFillRect(context, CGRectInset(frontIconRect, 1.0, 1.0));
  185. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  186. UIGraphicsEndImageContext();
  187. return image;
  188. }
  189. #pragma mark - Checking for Selection Limit
  190. - (BOOL)isMinimumSelectionLimitFulfilled
  191. {
  192. return (self.imagePickerController.minimumNumberOfSelection <= self.imagePickerController.selectedAssets.count);
  193. }
  194. - (BOOL)isMaximumSelectionLimitReached
  195. {
  196. NSUInteger minimumNumberOfSelection = MAX(1, self.imagePickerController.minimumNumberOfSelection);
  197. if (minimumNumberOfSelection <= self.imagePickerController.maximumNumberOfSelection) {
  198. return (self.imagePickerController.maximumNumberOfSelection <= self.imagePickerController.selectedAssets.count);
  199. }
  200. return NO;
  201. }
  202. - (void)updateControlState
  203. {
  204. self.doneButton.enabled = [self isMinimumSelectionLimitFulfilled];
  205. }
  206. #pragma mark - UITableViewDataSource
  207. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  208. {
  209. return 1;
  210. }
  211. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  212. {
  213. return self.assetCollections.count;
  214. }
  215. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  216. {
  217. UGCKitAlbumCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AlbumCell" forIndexPath:indexPath];
  218. cell.accessoryView = [[UIImageView alloc] initWithImage:_theme.rightArrowIcon];
  219. cell.selectionStyle = UITableViewCellSelectionStyleNone;
  220. cell.backgroundColor = [UIColor whiteColor];
  221. cell.tag = indexPath.row;
  222. cell.borderWidth = 1.0 / self.traitCollection.displayScale;
  223. // Thumbnail
  224. PHAssetCollection *assetCollection = self.assetCollections[indexPath.row];
  225. PHFetchOptions *options = [PHFetchOptions new];
  226. switch (self.imagePickerController.mediaType) {
  227. case UGCKitMediaTypePhoto:
  228. options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
  229. break;
  230. case UGCKitMediaTypeVideo:
  231. options.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo];
  232. break;
  233. default:
  234. break;
  235. }
  236. PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
  237. PHImageManager *imageManager = [PHImageManager defaultManager];
  238. if (fetchResult.count >= 3) {
  239. cell.imageView3.hidden = NO;
  240. [imageManager requestImageForAsset:fetchResult[fetchResult.count - 3]
  241. targetSize:CGSizeScale(cell.imageView3.frame.size, self.traitCollection.displayScale)
  242. contentMode:PHImageContentModeAspectFill
  243. options:nil
  244. resultHandler:^(UIImage *result, NSDictionary *info) {
  245. if (cell.tag == indexPath.row) {
  246. cell.imageView3.image = result;
  247. }
  248. }];
  249. } else {
  250. cell.imageView3.hidden = YES;
  251. }
  252. if (fetchResult.count >= 2) {
  253. cell.imageView2.hidden = NO;
  254. [imageManager requestImageForAsset:fetchResult[fetchResult.count - 2]
  255. targetSize:CGSizeScale(cell.imageView2.frame.size, self.traitCollection.displayScale)
  256. contentMode:PHImageContentModeAspectFill
  257. options:nil
  258. resultHandler:^(UIImage *result, NSDictionary *info) {
  259. if (cell.tag == indexPath.row) {
  260. cell.imageView2.image = result;
  261. }
  262. }];
  263. } else {
  264. cell.imageView2.hidden = YES;
  265. }
  266. if (fetchResult.count >= 1) {
  267. [imageManager requestImageForAsset:fetchResult[fetchResult.count - 1]
  268. targetSize:CGSizeScale(cell.imageView1.frame.size, self.traitCollection.displayScale)
  269. contentMode:PHImageContentModeAspectFill
  270. options:nil
  271. resultHandler:^(UIImage *result, NSDictionary *info) {
  272. if (cell.tag == indexPath.row) {
  273. cell.imageView1.image = result;
  274. }
  275. }];
  276. }
  277. if (fetchResult.count == 0) {
  278. cell.imageView3.hidden = NO;
  279. cell.imageView2.hidden = NO;
  280. // Set placeholder image
  281. UIImage *placeholderImage = [self placeholderImageWithSize:cell.imageView1.frame.size];
  282. cell.imageView1.image = placeholderImage;
  283. cell.imageView2.image = placeholderImage;
  284. cell.imageView3.image = placeholderImage;
  285. }
  286. // Album title
  287. cell.titleLabel.text = assetCollection.localizedTitle;
  288. // Number of photos
  289. cell.countLabel.text = [NSString stringWithFormat:@"%lu", (long)fetchResult.count];
  290. return cell;
  291. }
  292. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  293. {
  294. PHAssetCollection *collection = self.assetCollections[indexPath.row];
  295. if (self.onPickAssetCollection) {
  296. self.onPickAssetCollection(collection);
  297. }
  298. }
  299. #pragma mark - PHPhotoLibraryChangeObserver
  300. - (void)photoLibraryDidChange:(PHChange *)changeInstance
  301. {
  302. dispatch_async(dispatch_get_main_queue(), ^{
  303. // Update fetch results
  304. NSMutableArray *fetchResults = [self.fetchResults mutableCopy];
  305. [self.fetchResults enumerateObjectsUsingBlock:^(PHFetchResult *fetchResult, NSUInteger index, BOOL *stop) {
  306. PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:fetchResult];
  307. if (changeDetails) {
  308. [fetchResults replaceObjectAtIndex:index withObject:changeDetails.fetchResultAfterChanges];
  309. }
  310. }];
  311. if (![self.fetchResults isEqualToArray:fetchResults]) {
  312. self.fetchResults = fetchResults;
  313. // Reload albums
  314. [self updateAssetCollections];
  315. [self.tableView reloadData];
  316. }
  317. });
  318. }
  319. @end