FLEXCollectionContentSection.m 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. //
  2. // FLEXCollectionContentSection.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 8/28/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXCollectionContentSection.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXSubtitleTableViewCell.h"
  12. #import "FLEXTableView.h"
  13. #import "FLEXObjectExplorerFactory.h"
  14. typedef NS_ENUM(NSUInteger, FLEXCollectionType) {
  15. FLEXUnsupportedCollection,
  16. FLEXOrderedCollection,
  17. FLEXUnorderedCollection,
  18. FLEXKeyedCollection
  19. };
  20. @interface FLEXCollectionContentSection ()
  21. /// Generated from \c collectionFuture or \c collection
  22. @property (nonatomic, copy) id<FLEXCollection> cachedCollection;
  23. /// A static collection to display
  24. @property (nonatomic, readonly) id<FLEXCollection> collection;
  25. /// A collection that may change over time and can be called upon for new data
  26. @property (nonatomic, readonly) FLEXCollectionContentFuture collectionFuture;
  27. @property (nonatomic, readonly) FLEXCollectionType collectionType;
  28. @end
  29. @implementation FLEXCollectionContentSection
  30. @synthesize filterText = _filterText;
  31. #pragma mark Initialization
  32. + (instancetype)forObject:(id)object {
  33. return [self forCollection:object];
  34. }
  35. + (id)forCollection:(id<FLEXCollection>)collection {
  36. FLEXCollectionContentSection *section = [self new];
  37. section->_collectionType = [self typeForCollection:collection];
  38. section->_collection = collection;
  39. section.cachedCollection = collection;
  40. return section;
  41. }
  42. + (id)forReusableFuture:(FLEXCollectionContentFuture)collectionFuture {
  43. FLEXCollectionContentSection *section = [self new];
  44. section->_collectionFuture = collectionFuture;
  45. section.cachedCollection = collectionFuture(section);
  46. section->_collectionType = [self typeForCollection:section.cachedCollection];
  47. return section;
  48. }
  49. #pragma mark - Misc
  50. + (FLEXCollectionType)typeForCollection:(id<FLEXCollection>)collection {
  51. // Order matters here, as NSDictionary is keyed but it responds to allObjects
  52. if ([collection respondsToSelector:@selector(objectAtIndex:)]) {
  53. return FLEXOrderedCollection;
  54. }
  55. if ([collection respondsToSelector:@selector(objectForKey:)]) {
  56. return FLEXKeyedCollection;
  57. }
  58. if ([collection respondsToSelector:@selector(allObjects)]) {
  59. return FLEXUnorderedCollection;
  60. }
  61. [NSException raise:NSInvalidArgumentException
  62. format:@"Given collection does not properly conform to FLEXCollection"];
  63. return FLEXUnsupportedCollection;
  64. }
  65. /// Row titles
  66. /// - Ordered: the index
  67. /// - Unordered: the object
  68. /// - Keyed: the key
  69. - (NSString *)titleForRow:(NSInteger)row {
  70. switch (self.collectionType) {
  71. case FLEXOrderedCollection:
  72. if (!self.hideOrderIndexes) {
  73. return @(row).stringValue;
  74. }
  75. // Fall-through
  76. case FLEXUnorderedCollection:
  77. return [self describe:[self objectForRow:row]];
  78. case FLEXKeyedCollection:
  79. return [self describe:self.cachedCollection.allKeys[row]];
  80. case FLEXUnsupportedCollection:
  81. return nil;
  82. }
  83. }
  84. /// Row subtitles
  85. /// - Ordered: the object
  86. /// - Unordered: nothing
  87. /// - Keyed: the value
  88. - (NSString *)subtitleForRow:(NSInteger)row {
  89. switch (self.collectionType) {
  90. case FLEXOrderedCollection:
  91. if (!self.hideOrderIndexes) {
  92. nil;
  93. }
  94. // Fall-through
  95. case FLEXKeyedCollection:
  96. return [self describe:[self objectForRow:row]];
  97. case FLEXUnorderedCollection:
  98. return nil;
  99. case FLEXUnsupportedCollection:
  100. return nil;
  101. }
  102. }
  103. - (NSString *)describe:(id)object {
  104. return [FLEXRuntimeUtility summaryForObject:object];
  105. }
  106. - (id)objectForRow:(NSInteger)row {
  107. switch (self.collectionType) {
  108. case FLEXOrderedCollection:
  109. return self.cachedCollection[row];
  110. case FLEXUnorderedCollection:
  111. return self.cachedCollection.allObjects[row];
  112. case FLEXKeyedCollection:
  113. return self.cachedCollection[self.cachedCollection.allKeys[row]];
  114. case FLEXUnsupportedCollection:
  115. return nil;
  116. }
  117. }
  118. #pragma mark - Overrides
  119. - (NSString *)title {
  120. if (!self.hideSectionTitle) {
  121. if (self.customTitle) {
  122. return self.customTitle;
  123. }
  124. return FLEXPluralString(self.cachedCollection.count, @"Entries", @"Entry");
  125. }
  126. return nil;
  127. }
  128. - (NSInteger)numberOfRows {
  129. return self.cachedCollection.count;
  130. }
  131. - (void)setFilterText:(NSString *)filterText {
  132. super.filterText = filterText;
  133. if (filterText.length) {
  134. BOOL (^matcher)(id, id) = self.customFilter ?: ^BOOL(NSString *query, id obj) {
  135. return [[self describe:obj] localizedCaseInsensitiveContainsString:query];
  136. };
  137. NSPredicate *filter = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
  138. return matcher(filterText, obj);
  139. }];
  140. id<FLEXMutableCollection> tmp = self.cachedCollection.mutableCopy;
  141. [tmp filterUsingPredicate:filter];
  142. self.cachedCollection = tmp;
  143. } else {
  144. self.cachedCollection = self.collection ?: self.collectionFuture(self);
  145. }
  146. }
  147. - (void)reloadData {
  148. if (self.collectionFuture) {
  149. self.cachedCollection = self.collectionFuture(self);
  150. } else {
  151. self.cachedCollection = self.collection.copy;
  152. }
  153. }
  154. - (BOOL)canSelectRow:(NSInteger)row {
  155. return YES;
  156. }
  157. - (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
  158. return [FLEXObjectExplorerFactory explorerViewControllerForObject:[self objectForRow:row]];
  159. }
  160. - (NSString *)reuseIdentifierForRow:(NSInteger)row {
  161. return kFLEXDetailCell;
  162. }
  163. - (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
  164. cell.titleLabel.text = [self titleForRow:row];
  165. cell.subtitleLabel.text = [self subtitleForRow:row];
  166. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  167. }
  168. @end
  169. #pragma mark - NSMutableDictionary
  170. @implementation NSMutableDictionary (FLEXMutableCollection)
  171. - (void)filterUsingPredicate:(NSPredicate *)predicate {
  172. id test = ^BOOL(id key, NSUInteger idx, BOOL *stop) {
  173. if ([predicate evaluateWithObject:key]) {
  174. return NO;
  175. }
  176. return ![predicate evaluateWithObject:self[key]];
  177. };
  178. NSArray *keys = self.allKeys;
  179. NSIndexSet *remove = [keys indexesOfObjectsPassingTest:test];
  180. [self removeObjectsForKeys:[keys objectsAtIndexes:remove]];
  181. }
  182. @end