CTAssetCollectionViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. MIT License (MIT)
  3. Copyright (c) 2015 Clement CN Tsang
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. #import "CTAssetsPickerDefines.h"
  21. #import "CTAssetsPickerController.h"
  22. #import "CTAssetsPickerController+Internal.h"
  23. #import "CTAssetCollectionViewController.h"
  24. #import "CTAssetCollectionViewCell.h"
  25. #import "CTAssetsGridViewController.h"
  26. #import "PHAssetCollection+CTAssetsPickerController.h"
  27. #import "PHAsset+CTAssetsPickerController.h"
  28. #import "PHImageManager+CTAssetsPickerController.h"
  29. #import "NSBundle+CTAssetsPickerController.h"
  30. @interface CTAssetCollectionViewController()
  31. <PHPhotoLibraryChangeObserver, CTAssetsGridViewControllerDelegate>
  32. @property (nonatomic, weak) CTAssetsPickerController *picker;
  33. @property (nonatomic, strong) UIBarButtonItem *cancelButton;
  34. @property (nonatomic, strong) UIBarButtonItem *doneButton;
  35. @property (nonatomic, copy) NSArray *fetchResults;
  36. @property (nonatomic, copy) NSArray *assetCollections;
  37. @property (nonatomic, strong) PHCachingImageManager *imageManager;
  38. @property (nonatomic, strong) PHAssetCollection *defaultAssetCollection;
  39. @property (nonatomic, assign) BOOL didShowDefaultAssetCollection;
  40. @property (nonatomic, assign) BOOL didSelectDefaultAssetCollection;
  41. @end
  42. @implementation CTAssetCollectionViewController
  43. - (instancetype)init
  44. {
  45. if (self = [super initWithStyle:UITableViewStylePlain])
  46. {
  47. _imageManager = [PHCachingImageManager new];
  48. [self addNotificationObserver];
  49. }
  50. return self;
  51. }
  52. - (void)viewDidLoad
  53. {
  54. [super viewDidLoad];
  55. [self setupViews];
  56. [self localize];
  57. [self setupDefaultAssetCollection];
  58. [self setupFetchResults];
  59. [self registerChangeObserver];
  60. }
  61. - (void)viewWillAppear:(BOOL)animated
  62. {
  63. [super viewWillAppear:animated];
  64. [self setupButtons];
  65. [self updateTitle:self.picker.selectedAssets];
  66. [self updateButton:self.picker.selectedAssets];
  67. [self selectDefaultAssetCollection];
  68. }
  69. - (void)dealloc
  70. {
  71. [self unregisterChangeObserver];
  72. [self removeNotificationObserver];
  73. }
  74. #pragma mark - Reload user interface
  75. - (void)reloadUserInterface
  76. {
  77. [self setupViews];
  78. [self setupButtons];
  79. [self localize];
  80. [self setupDefaultAssetCollection];
  81. [self setupFetchResults];
  82. }
  83. #pragma mark - Accessors
  84. - (CTAssetsPickerController *)picker
  85. {
  86. return (CTAssetsPickerController *)self.splitViewController.parentViewController;
  87. }
  88. - (NSIndexPath *)indexPathForAssetCollection:(PHAssetCollection *)assetCollection
  89. {
  90. NSInteger row = [self.assetCollections indexOfObject:assetCollection];
  91. if (row != NSNotFound)
  92. return [NSIndexPath indexPathForRow:row inSection:0];
  93. else
  94. return nil;
  95. }
  96. #pragma mark - Setup
  97. - (void)setupViews
  98. {
  99. self.tableView.rowHeight = UITableViewAutomaticDimension;
  100. self.tableView.estimatedRowHeight =
  101. self.picker.assetCollectionThumbnailSize.height +
  102. self.tableView.layoutMargins.top +
  103. self.tableView.layoutMargins.bottom;
  104. self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  105. }
  106. - (void)setupButtons
  107. {
  108. if (self.doneButton == nil)
  109. {
  110. NSString *title = (self.picker.doneButtonTitle) ?
  111. self.picker.doneButtonTitle : CTAssetsPickerLocalizedString(@"Done", nil);
  112. self.doneButton =
  113. [[UIBarButtonItem alloc] initWithTitle:title
  114. style:UIBarButtonItemStyleDone
  115. target:self.picker
  116. action:@selector(finishPickingAssets:)];
  117. }
  118. if (self.cancelButton == nil)
  119. {
  120. self.cancelButton =
  121. [[UIBarButtonItem alloc] initWithTitle:CTAssetsPickerLocalizedString(@"Cancel", nil)
  122. style:UIBarButtonItemStylePlain
  123. target:self.picker
  124. action:@selector(dismiss:)];
  125. }
  126. }
  127. - (void)localize
  128. {
  129. [self resetTitle];
  130. }
  131. - (void)setupFetchResults
  132. {
  133. NSMutableArray *fetchResults = [NSMutableArray new];
  134. for (NSNumber *subtypeNumber in self.picker.assetCollectionSubtypes)
  135. {
  136. PHAssetCollectionType type = [PHAssetCollection ctassetPickerAssetCollectionTypeOfSubtype:subtypeNumber.integerValue];
  137. PHAssetCollectionSubtype subtype = subtypeNumber.integerValue;
  138. PHFetchResult *fetchResult =
  139. [PHAssetCollection fetchAssetCollectionsWithType:type
  140. subtype:subtype
  141. options:self.picker.assetCollectionFetchOptions];
  142. [fetchResults addObject:fetchResult];
  143. }
  144. self.fetchResults = [NSMutableArray arrayWithArray:fetchResults];
  145. [self updateAssetCollections];
  146. [self reloadData];
  147. [self showDefaultAssetCollection];
  148. }
  149. - (void)updateAssetCollections
  150. {
  151. NSMutableArray *assetCollections = [NSMutableArray new];
  152. for (PHFetchResult *fetchResult in self.fetchResults)
  153. {
  154. for (PHAssetCollection *assetCollection in fetchResult)
  155. {
  156. BOOL showsAssetCollection = YES;
  157. if (!self.picker.showsEmptyAlbums)
  158. {
  159. PHFetchOptions *options = [PHFetchOptions new];
  160. options.predicate = self.picker.assetsFetchOptions.predicate;
  161. if ([options respondsToSelector:@selector(setFetchLimit:)])
  162. options.fetchLimit = 1;
  163. NSInteger count = [assetCollection ctassetPikcerCountOfAssetsFetchedWithOptions:options];
  164. showsAssetCollection = (count > 0);
  165. }
  166. if (showsAssetCollection)
  167. [assetCollections addObject:assetCollection];
  168. }
  169. }
  170. self.assetCollections = [NSMutableArray arrayWithArray:assetCollections];
  171. }
  172. - (void)setupDefaultAssetCollection
  173. {
  174. if (!self.picker || self.picker.defaultAssetCollection == PHAssetCollectionSubtypeAny) {
  175. self.defaultAssetCollection = nil;
  176. return;
  177. }
  178. PHAssetCollectionType type = [PHAssetCollection ctassetPickerAssetCollectionTypeOfSubtype:self.picker.defaultAssetCollection];
  179. PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:type subtype:self.picker.defaultAssetCollection options:self.picker.assetCollectionFetchOptions];
  180. self.defaultAssetCollection = fetchResult.firstObject;
  181. }
  182. #pragma mark - Rotation
  183. - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
  184. {
  185. [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
  186. [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  187. [self updateTitle:self.picker.selectedAssets];
  188. [self updateButton:self.picker.selectedAssets];
  189. } completion:nil];
  190. }
  191. #pragma mark - Notifications
  192. - (void)addNotificationObserver
  193. {
  194. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  195. [center addObserver:self
  196. selector:@selector(selectedAssetsChanged:)
  197. name:CTAssetsPickerSelectedAssetsDidChangeNotification
  198. object:nil];
  199. [center addObserver:self
  200. selector:@selector(contentSizeCategoryChanged:)
  201. name:UIContentSizeCategoryDidChangeNotification
  202. object:nil];
  203. }
  204. - (void)removeNotificationObserver
  205. {
  206. [[NSNotificationCenter defaultCenter] removeObserver:self name:CTAssetsPickerSelectedAssetsDidChangeNotification object:nil];
  207. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIContentSizeCategoryDidChangeNotification object:nil];
  208. }
  209. #pragma mark - Photo library change observer
  210. - (void)registerChangeObserver
  211. {
  212. [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
  213. }
  214. - (void)unregisterChangeObserver
  215. {
  216. [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
  217. }
  218. #pragma mark - Photo library changed
  219. - (void)photoLibraryDidChange:(PHChange *)changeInstance
  220. {
  221. // Call might come on any background queue. Re-dispatch to the main queue to handle it.
  222. dispatch_async(dispatch_get_main_queue(), ^{
  223. NSMutableArray *updatedFetchResults = nil;
  224. for (PHFetchResult *fetchResult in self.fetchResults)
  225. {
  226. PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:fetchResult];
  227. if (changeDetails)
  228. {
  229. if (!updatedFetchResults)
  230. updatedFetchResults = [self.fetchResults mutableCopy];
  231. updatedFetchResults[[self.fetchResults indexOfObject:fetchResult]] = changeDetails.fetchResultAfterChanges;
  232. }
  233. }
  234. if (updatedFetchResults)
  235. {
  236. self.fetchResults = updatedFetchResults;
  237. [self updateAssetCollections];
  238. [self reloadData];
  239. }
  240. });
  241. }
  242. #pragma mark - Selected assets changed
  243. - (void)selectedAssetsChanged:(NSNotification *)notification
  244. {
  245. NSArray *selectedAssets = (NSArray *)notification.object;
  246. [self updateTitle:selectedAssets];
  247. [self updateButton:selectedAssets];
  248. }
  249. - (void)updateTitle:(NSArray *)selectedAssets
  250. {
  251. if ([self isTopViewController] && selectedAssets.count > 0)
  252. self.title = self.picker.selectedAssetsString;
  253. else
  254. [self resetTitle];
  255. }
  256. - (void)updateButton:(NSArray *)selectedAssets
  257. {
  258. self.navigationItem.leftBarButtonItem = (self.picker.showsCancelButton) ? self.cancelButton : nil;
  259. self.navigationItem.rightBarButtonItem = [self isTopViewController] ? self.doneButton : nil;
  260. if (self.picker.alwaysEnableDoneButton)
  261. self.navigationItem.rightBarButtonItem.enabled = YES;
  262. else
  263. self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
  264. }
  265. - (BOOL)isTopViewController
  266. {
  267. UIViewController *vc = self.splitViewController.viewControllers.lastObject;
  268. if ([vc isMemberOfClass:[UINavigationController class]])
  269. return (self == ((UINavigationController *)vc).topViewController);
  270. else
  271. return NO;
  272. }
  273. - (void)resetTitle
  274. {
  275. if (!self.picker.title)
  276. self.title = CTAssetsPickerLocalizedString(@"Photos", nil);
  277. else
  278. self.title = self.picker.title;
  279. }
  280. #pragma mark - Content size category changed
  281. - (void)contentSizeCategoryChanged:(NSNotification *)notification
  282. {
  283. [self reloadData];
  284. }
  285. #pragma mark - Reload data
  286. - (void)reloadData
  287. {
  288. if (self.assetCollections.count > 0)
  289. [self.tableView reloadData];
  290. else
  291. [self.picker showNoAssets];
  292. }
  293. #pragma mark - Table view data source
  294. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  295. {
  296. return 1;
  297. }
  298. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  299. {
  300. return self.assetCollections.count;
  301. }
  302. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  303. {
  304. PHAssetCollection *collection = self.assetCollections[indexPath.row];
  305. NSUInteger count;
  306. if (self.picker.showsNumberOfAssets)
  307. count = [collection ctassetPikcerCountOfAssetsFetchedWithOptions:self.picker.assetsFetchOptions];
  308. else
  309. count = NSNotFound;
  310. static NSString *cellIdentifier = @"CellIdentifier";
  311. CTAssetCollectionViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  312. if (cell == nil)
  313. cell = [[CTAssetCollectionViewCell alloc] initWithThumbnailSize:self.picker.assetCollectionThumbnailSize
  314. reuseIdentifier:cellIdentifier];
  315. [cell bind:collection count:count];
  316. [self requestThumbnailsForCell:cell assetCollection:collection];
  317. return cell;
  318. }
  319. - (void)requestThumbnailsForCell:(CTAssetCollectionViewCell *)cell assetCollection:(PHAssetCollection *)collection
  320. {
  321. NSUInteger count = cell.thumbnailStacks.thumbnailViews.count;
  322. NSArray *assets = [self posterAssetsFromAssetCollection:collection count:count];
  323. CGSize targetSize = [self.picker imageSizeForContainerSize:self.picker.assetCollectionThumbnailSize];
  324. for (NSUInteger index = 0; index < count; index++)
  325. {
  326. CTAssetThumbnailView *thumbnailView = [cell.thumbnailStacks thumbnailAtIndex:index];
  327. thumbnailView.hidden = (assets.count > 0) ? YES : NO;
  328. if (index < assets.count)
  329. {
  330. PHAsset *asset = assets[index];
  331. [self.imageManager ctassetsPickerRequestImageForAsset:asset
  332. targetSize:targetSize
  333. contentMode:PHImageContentModeAspectFill
  334. options:self.picker.thumbnailRequestOptions
  335. resultHandler:^(UIImage *image, NSDictionary *info){
  336. [thumbnailView setHidden:NO];
  337. [thumbnailView bind:image assetCollection:collection];
  338. }];
  339. }
  340. }
  341. }
  342. - (NSArray *)posterAssetsFromAssetCollection:(PHAssetCollection *)collection count:(NSUInteger)count;
  343. {
  344. PHFetchOptions *options = [PHFetchOptions new];
  345. options.predicate = self.picker.assetsFetchOptions.predicate; // aligned specified predicate
  346. options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
  347. PHFetchResult *result = [PHAsset fetchKeyAssetsInAssetCollection:collection options:options];
  348. NSUInteger location = 0;
  349. NSUInteger length = (result.count < count) ? result.count : count;
  350. NSArray *assets = [self itemsFromFetchResult:result range:NSMakeRange(location, length)];
  351. return assets;
  352. }
  353. - (NSArray *)itemsFromFetchResult:(PHFetchResult *)result range:(NSRange)range
  354. {
  355. if (result.count == 0)
  356. return nil;
  357. NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
  358. NSArray *array = [result objectsAtIndexes:indexSet];
  359. return array;
  360. }
  361. #pragma mark - Table view delegate
  362. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  363. {
  364. PHAssetCollection *collection = self.assetCollections[indexPath.row];
  365. CTAssetsGridViewController *vc = [CTAssetsGridViewController new];
  366. vc.title = self.picker.selectedAssetsString ? : collection.localizedTitle;
  367. vc.assetCollection = collection;
  368. vc.delegate = self;
  369. UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
  370. nav.delegate = (id<UINavigationControllerDelegate>)self.picker;
  371. [self.picker setShouldCollapseDetailViewController:NO];
  372. [self.splitViewController showDetailViewController:nav sender:nil];
  373. }
  374. #pragma mark - Show / select default asset collection
  375. - (void)showDefaultAssetCollection
  376. {
  377. if (self.defaultAssetCollection && !self.didShowDefaultAssetCollection)
  378. {
  379. CTAssetsGridViewController *vc = [CTAssetsGridViewController new];
  380. vc.title = self.picker.selectedAssetsString ? : self.defaultAssetCollection.localizedTitle;
  381. vc.assetCollection = self.defaultAssetCollection;
  382. vc.delegate = self;
  383. UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
  384. nav.delegate = (id<UINavigationControllerDelegate>)self.picker;
  385. [self.picker setShouldCollapseDetailViewController:(self.picker.modalPresentationStyle == UIModalPresentationFormSheet)];
  386. [self.splitViewController showDetailViewController:nav sender:nil];
  387. NSIndexPath *indexPath = [self indexPathForAssetCollection:self.defaultAssetCollection];
  388. [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
  389. self.didShowDefaultAssetCollection = YES;
  390. }
  391. }
  392. - (void)selectDefaultAssetCollection
  393. {
  394. if (self.defaultAssetCollection && !self.didSelectDefaultAssetCollection)
  395. {
  396. NSIndexPath *indexPath = [self indexPathForAssetCollection:self.defaultAssetCollection];
  397. if (indexPath)
  398. {
  399. [UIView animateWithDuration:0.0f
  400. animations:^{
  401. [self.tableView selectRowAtIndexPath:indexPath
  402. animated:(!self.splitViewController.collapsed)
  403. scrollPosition:UITableViewScrollPositionTop];
  404. }
  405. completion:^(BOOL finished){
  406. // mimic clearsSelectionOnViewWillAppear
  407. if (finished && self.splitViewController.collapsed)
  408. [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
  409. }];
  410. }
  411. self.didSelectDefaultAssetCollection = YES;
  412. }
  413. }
  414. #pragma mark - Grid view controller delegate
  415. - (void)assetsGridViewController:(CTAssetsGridViewController *)picker photoLibraryDidChangeForAssetCollection:(PHAssetCollection *)assetCollection
  416. {
  417. NSIndexPath *indexPath = [self indexPathForAssetCollection:assetCollection];
  418. if (indexPath)
  419. {
  420. [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
  421. [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
  422. }
  423. }
  424. @end