FLEXMetadataSection.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // FLEXMetadataSection.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 9/19/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXMetadataSection.h"
  9. #import "FLEXTableView.h"
  10. #import "FLEXTableViewCell.h"
  11. #import "FLEXObjectExplorerFactory.h"
  12. #import "FLEXFieldEditorViewController.h"
  13. #import "FLEXMethodCallingViewController.h"
  14. #import "FLEXIvar.h"
  15. #import "NSArray+Functional.h"
  16. #import "FLEXRuntime+UIKitHelpers.h"
  17. @interface FLEXMetadataSection ()
  18. @property (nonatomic, readonly) FLEXObjectExplorer *explorer;
  19. /// Filtered
  20. @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *metadata;
  21. /// Unfiltered
  22. @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *allMetadata;
  23. @end
  24. @implementation FLEXMetadataSection
  25. #pragma mark - Initialization
  26. + (instancetype)explorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
  27. return [[self alloc] initWithExplorer:explorer kind:metadataKind];
  28. }
  29. - (id)initWithExplorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
  30. self = [super init];
  31. if (self) {
  32. _explorer = explorer;
  33. _metadataKind = metadataKind;
  34. [self reloadData];
  35. }
  36. return self;
  37. }
  38. #pragma mark - Private
  39. - (NSString *)titleWithBaseName:(NSString *)baseName {
  40. unsigned long totalCount = self.allMetadata.count;
  41. unsigned long filteredCount = self.metadata.count;
  42. if (totalCount == filteredCount) {
  43. return [baseName stringByAppendingFormat:@" (%lu)", totalCount];
  44. } else {
  45. return [baseName stringByAppendingFormat:@" (%lu of %lu)", filteredCount, totalCount];
  46. }
  47. }
  48. - (__kindof UIViewController *)detailScreenForMetadata:(id)metadata {
  49. id target = self.explorer.object;
  50. switch (self.metadataKind) {
  51. case FLEXMetadataKindProperties:
  52. case FLEXMetadataKindIvars: {
  53. // This works for both properties and ivars, we just
  54. // cast so the compiler knows what return type to expect since
  55. // this method has the same name as -[NSValue getValue:] (void *)
  56. id currentValue = [(FLEXIvar *)metadata getPotentiallyUnboxedValue:target];
  57. return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  58. }
  59. case FLEXMetadataKindClassMethods:
  60. // Make sure the target is a class
  61. // for class methods, then fall through
  62. if (self.explorer.objectIsInstance && ![metadata isInstanceMethod]) {
  63. target = [target class];
  64. }
  65. case FLEXMetadataKindMethods: {
  66. return [FLEXMethodCallingViewController target:target method:metadata];
  67. }
  68. }
  69. }
  70. // Only for properties or ivars
  71. - (id)valueForRow:(NSInteger)row {
  72. id metadata = self.metadata[row];
  73. // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
  74. // because we want to "preview" what object is being stored if this is
  75. // a void * or something and we're given an NSValue back from getValue:
  76. switch (self.metadataKind) {
  77. case FLEXMetadataKindProperties:
  78. return [self.explorer valueForProperty:metadata];
  79. case FLEXMetadataKindIvars:
  80. return [self.explorer valueForIvar:metadata];
  81. // Methods: nil
  82. case FLEXMetadataKindMethods:
  83. case FLEXMetadataKindClassMethods:
  84. return nil;
  85. }
  86. }
  87. - (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
  88. return [self.metadata[row] suggestedAccessoryTypeWithTarget:self.explorer.object];
  89. }
  90. #pragma mark - Public
  91. - (void)setExcludedMetadata:(NSSet<NSString *> *)excludedMetadata {
  92. _excludedMetadata = excludedMetadata;
  93. [self reloadData];
  94. }
  95. #pragma mark - Overrides
  96. - (NSString *)titleForRow:(NSInteger)row {
  97. return [self.metadata[row] description];
  98. }
  99. - (NSString *)subtitleForRow:(NSInteger)row {
  100. id metadata = self.metadata[row];
  101. // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
  102. // because we want to "preview" what object is being stored if this is
  103. // a void * or something and we're given an NSValue back from getValue:
  104. switch (self.metadataKind) {
  105. case FLEXMetadataKindProperties:
  106. case FLEXMetadataKindIvars:
  107. #warning TODO: fix this logic for class properties
  108. if (!self.explorer.objectIsInstance) {
  109. if (self.metadataKind == FLEXMetadataKindProperties) {
  110. if (![metadata isClassProperty]) {
  111. return nil;
  112. }
  113. } else {
  114. return nil;
  115. }
  116. }
  117. return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
  118. case FLEXMetadataKindMethods:
  119. case FLEXMetadataKindClassMethods:
  120. return [metadata selectorString];
  121. default:
  122. return nil;
  123. }
  124. }
  125. - (NSString *)title {
  126. switch (self.metadataKind) {
  127. case FLEXMetadataKindProperties:
  128. return [self titleWithBaseName:@"Properties"];
  129. case FLEXMetadataKindIvars:
  130. return [self titleWithBaseName:@"Ivars"];
  131. case FLEXMetadataKindMethods:
  132. return [self titleWithBaseName:@"Methods"];
  133. case FLEXMetadataKindClassMethods:
  134. return [self titleWithBaseName:@"Class methods"];
  135. default: return nil;
  136. }
  137. }
  138. - (NSInteger)numberOfRows {
  139. return self.metadata.count;
  140. }
  141. - (void)setFilterText:(NSString *)filterText {
  142. super.filterText = filterText;
  143. if (!self.filterText.length) {
  144. self.metadata = self.allMetadata;
  145. } else {
  146. self.metadata = [self.allMetadata flex_filtered:^BOOL(FLEXProperty *obj, NSUInteger idx) {
  147. return [obj.description localizedCaseInsensitiveContainsString:self.filterText];
  148. }];
  149. }
  150. }
  151. - (void)reloadData {
  152. switch (self.metadataKind) {
  153. case FLEXMetadataKindProperties:
  154. self.allMetadata = self.explorer.properties;
  155. break;
  156. case FLEXMetadataKindIvars:
  157. self.allMetadata = self.explorer.ivars;
  158. break;
  159. case FLEXMetadataKindMethods:
  160. self.allMetadata = self.explorer.methods;
  161. break;
  162. case FLEXMetadataKindClassMethods:
  163. self.allMetadata = self.explorer.classMethods;
  164. break;
  165. }
  166. // Remove excluded metadata
  167. if (self.excludedMetadata.count) {
  168. id filterBlock = ^BOOL(id obj, NSUInteger idx) {
  169. return ![self.excludedMetadata containsObject:[obj name]];
  170. };
  171. // Filter exclusions and sort
  172. self.allMetadata = [[self.allMetadata flex_filtered:filterBlock]
  173. sortedArrayUsingSelector:@selector(compare:)
  174. ];
  175. }
  176. // Re-filter data
  177. self.filterText = self.filterText;
  178. }
  179. - (BOOL)canSelectRow:(NSInteger)row {
  180. switch (self.metadataKind) {
  181. case FLEXMetadataKindProperties:
  182. case FLEXMetadataKindIvars:
  183. if (![self valueForRow:row]) {
  184. return NO;
  185. }
  186. case FLEXMetadataKindMethods:
  187. return self.explorer.objectIsInstance;
  188. case FLEXMetadataKindClassMethods:
  189. return YES;
  190. default: return NO;
  191. }
  192. }
  193. - (NSString *)reuseIdentifierForRow:(NSInteger)row {
  194. return kFLEXDetailCell;
  195. }
  196. - (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
  197. return [self detailScreenForMetadata:self.metadata[row]];
  198. }
  199. - (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
  200. return ^(UIViewController *host) {
  201. [host.navigationController pushViewController:[self editorForRow:row] animated:YES];
  202. };
  203. }
  204. - (UIViewController *)editorForRow:(NSInteger)row {
  205. NSAssert(
  206. self.metadataKind == FLEXMetadataKindIvars ||
  207. self.metadataKind == FLEXMetadataKindProperties, // ||,
  208. // self.metadataKind == FLEXMetadataKindClassProperties,
  209. @"Only ivars or properties can be edited"
  210. );
  211. id metadata = self.metadata[row];
  212. // Nil editor means unsupported ivar or property type, or nil value
  213. if (self.metadataKind == FLEXMetadataKindProperties) {
  214. return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
  215. } else if (self.metadataKind == FLEXMetadataKindIvars) {
  216. return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
  217. }
  218. // TODO: support class properties
  219. @throw NSInternalInconsistencyException;
  220. return nil;
  221. }
  222. - (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
  223. cell.titleLabel.text = [self titleForRow:row];
  224. cell.subtitleLabel.text = [self subtitleForRow:row];
  225. cell.accessoryType = [self accessoryTypeForRow:row];
  226. }
  227. @end