FLEXObjectExplorerViewController.m 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. //
  2. // FLEXObjectExplorerViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 2014-05-03.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXObjectExplorerViewController.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXDescriptionTableViewCell.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXPropertyEditorViewController.h"
  14. #import "FLEXIvarEditorViewController.h"
  15. #import "FLEXMethodCallingViewController.h"
  16. #import <objc/runtime.h>
  17. typedef NS_ENUM(NSUInteger, FLEXObjectExplorerSection) {
  18. FLEXObjectExplorerSectionDescription,
  19. FLEXObjectExplorerSectionCustom,
  20. FLEXObjectExplorerSectionProperties,
  21. FLEXObjectExplorerSectionIvars,
  22. FLEXObjectExplorerSectionMethods,
  23. FLEXObjectExplorerSectionClassMethods
  24. };
  25. // Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
  26. @interface FLEXPropertyBox : NSObject
  27. @property (nonatomic, assign) objc_property_t property;
  28. @end
  29. @implementation FLEXPropertyBox
  30. @end
  31. @interface FLEXIvarBox : NSObject
  32. @property (nonatomic, assign) Ivar ivar;
  33. @end
  34. @implementation FLEXIvarBox
  35. @end
  36. @interface FLEXMethodBox : NSObject
  37. @property (nonatomic, assign) Method method;
  38. @end
  39. @implementation FLEXMethodBox
  40. @end
  41. static const NSInteger kFLEXObjectExplorerScopeNoInheritanceIndex = 0;
  42. static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
  43. @interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
  44. @property (nonatomic, strong) NSArray *properties;
  45. @property (nonatomic, strong) NSArray *inheritedProperties;
  46. @property (nonatomic, strong) NSArray *filteredProperties;
  47. @property (nonatomic, strong) NSArray *ivars;
  48. @property (nonatomic, strong) NSArray *inheritedIvars;
  49. @property (nonatomic, strong) NSArray *filteredIvars;
  50. @property (nonatomic, strong) NSArray *methods;
  51. @property (nonatomic, strong) NSArray *inheritedMethods;
  52. @property (nonatomic, strong) NSArray *filteredMethods;
  53. @property (nonatomic, strong) NSArray *classMethods;
  54. @property (nonatomic, strong) NSArray *inheritedClassMethods;
  55. @property (nonatomic, strong) NSArray *filteredClassMethods;
  56. @property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
  57. @property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
  58. @property (nonatomic, strong) UISearchBar *searchBar;
  59. @property (nonatomic, strong) NSString *filterText;
  60. @property (nonatomic, assign) BOOL includeInheritance;
  61. @end
  62. @implementation FLEXObjectExplorerViewController
  63. - (id)initWithStyle:(UITableViewStyle)style
  64. {
  65. // Force grouped style
  66. return [super initWithStyle:UITableViewStyleGrouped];
  67. }
  68. - (void)viewDidLoad
  69. {
  70. [super viewDidLoad];
  71. self.searchBar = [[UISearchBar alloc] init];
  72. self.searchBar.placeholder = @"Filter";
  73. self.searchBar.delegate = self;
  74. self.searchBar.showsScopeBar = YES;
  75. self.searchBar.scopeButtonTitles = @[@"No Inheritance", @"Include Inheritance"];
  76. [self.searchBar sizeToFit];
  77. self.tableView.tableHeaderView = self.searchBar;
  78. }
  79. - (void)viewWillAppear:(BOOL)animated
  80. {
  81. NSMutableArray *visibleIndexPaths = [NSMutableArray array];
  82. for (UITableViewCell *cell in [self.tableView visibleCells]) {
  83. [visibleIndexPaths addObject:[self.tableView indexPathForCell:cell]];
  84. }
  85. [self.tableView reloadRowsAtIndexPaths:visibleIndexPaths withRowAnimation:UITableViewRowAnimationNone];
  86. }
  87. - (BOOL)prefersStatusBarHidden
  88. {
  89. return YES;
  90. }
  91. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  92. {
  93. [self.searchBar endEditing:YES];
  94. }
  95. #pragma mark - Search
  96. - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
  97. {
  98. self.filterText = searchText;
  99. }
  100. - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
  101. {
  102. if (selectedScope == kFLEXObjectExplorerScopeIncludeInheritanceIndex) {
  103. self.includeInheritance = YES;
  104. } else if (selectedScope == kFLEXObjectExplorerScopeNoInheritanceIndex) {
  105. self.includeInheritance = NO;
  106. }
  107. }
  108. #pragma mark - Setter overrides
  109. - (void)setObject:(id)object
  110. {
  111. _object = object;
  112. self.title = [[object class] description];
  113. [self updateTableData];
  114. }
  115. - (void)setIncludeInheritance:(BOOL)includeInheritance
  116. {
  117. if (_includeInheritance != includeInheritance) {
  118. _includeInheritance = includeInheritance;
  119. [self updateDisplayedData];
  120. }
  121. }
  122. - (void)setFilterText:(NSString *)filterText
  123. {
  124. if (_filterText != filterText || ![_filterText isEqual:filterText]) {
  125. _filterText = filterText;
  126. [self updateDisplayedData];
  127. }
  128. }
  129. #pragma mark - Reloading
  130. - (void)updateTableData
  131. {
  132. [self updateCustomData];
  133. [self updateProperties];
  134. [self updateIvars];
  135. [self updateMethods];
  136. [self updateClassMethods];
  137. [self updateDisplayedData];
  138. }
  139. - (void)updateDisplayedData
  140. {
  141. [self updateFilteredCustomData];
  142. [self updateFilteredProperties];
  143. [self updateFilteredIvars];
  144. [self updateFilteredMethods];
  145. [self updateFilteredClassMethods];
  146. [self.tableView reloadData];
  147. }
  148. - (BOOL)objectIsClass
  149. {
  150. return self.object && [self.object class] == self.object;
  151. }
  152. - (BOOL)shouldShowDescription
  153. {
  154. return ![self objectIsClass] && [[FLEXUtility safeDescriptionForObject:self.object] length] > 0;
  155. }
  156. #pragma mark - Properties
  157. - (void)updateProperties
  158. {
  159. Class class = [self.object class];
  160. self.properties = [[self class] propertiesForClass:class];
  161. self.inheritedProperties = [[self class] inheritedPropertiesForClass:class];
  162. }
  163. + (NSArray *)propertiesForClass:(Class)class
  164. {
  165. NSMutableArray *boxedProperties = [NSMutableArray array];
  166. unsigned int propertyCount = 0;
  167. objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
  168. if (propertyList) {
  169. for (int i = 0; i < propertyCount; i++) {
  170. FLEXPropertyBox *propertyBox = [[FLEXPropertyBox alloc] init];
  171. propertyBox.property = propertyList[i];
  172. [boxedProperties addObject:propertyBox];
  173. }
  174. free(propertyList);
  175. }
  176. return boxedProperties;
  177. }
  178. + (NSArray *)inheritedPropertiesForClass:(Class)class
  179. {
  180. NSMutableArray *inheritedProperties = [NSMutableArray array];
  181. while ((class = [class superclass])) {
  182. [inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
  183. }
  184. return inheritedProperties;
  185. }
  186. - (void)updateFilteredProperties
  187. {
  188. NSArray *candidateProperties = self.properties;
  189. if (self.includeInheritance) {
  190. candidateProperties = [candidateProperties arrayByAddingObjectsFromArray:self.inheritedProperties];
  191. }
  192. NSArray *unsortedFilteredProperties = nil;
  193. if ([self.filterText length] > 0) {
  194. NSMutableArray *mutableUnsortedFilteredProperties = [NSMutableArray array];
  195. for (FLEXPropertyBox *propertyBox in candidateProperties) {
  196. NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  197. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  198. [mutableUnsortedFilteredProperties addObject:propertyBox];
  199. }
  200. }
  201. unsortedFilteredProperties = mutableUnsortedFilteredProperties;
  202. } else {
  203. unsortedFilteredProperties = candidateProperties;
  204. }
  205. self.filteredProperties = [unsortedFilteredProperties sortedArrayUsingComparator:^NSComparisonResult(FLEXPropertyBox *propertyBox1, FLEXPropertyBox *propertyBox2) {
  206. NSString *name1 = [NSString stringWithUTF8String:property_getName(propertyBox1.property)];
  207. NSString *name2 = [NSString stringWithUTF8String:property_getName(propertyBox2.property)];
  208. return [name1 caseInsensitiveCompare:name2];
  209. }];
  210. }
  211. - (NSString *)titleForPropertyAtIndex:(NSInteger)index
  212. {
  213. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
  214. return [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  215. }
  216. - (id)valueForPropertyAtIndex:(NSInteger)index
  217. {
  218. id value = nil;
  219. if (![self objectIsClass]) {
  220. FLEXPropertyBox *propertyBox = [self.filteredProperties objectAtIndex:index];
  221. value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
  222. }
  223. return value;
  224. }
  225. #pragma mark - Ivars
  226. - (void)updateIvars
  227. {
  228. Class class = [self.object class];
  229. self.ivars = [[self class] ivarsForClass:class];
  230. self.inheritedIvars = [[self class] inheritedIvarsForClass:class];
  231. }
  232. + (NSArray *)ivarsForClass:(Class)class
  233. {
  234. NSMutableArray *boxedIvars = [NSMutableArray array];
  235. unsigned int ivarCount = 0;
  236. Ivar *ivarList = class_copyIvarList(class, &ivarCount);
  237. if (ivarList) {
  238. for (int i = 0; i < ivarCount; i++) {
  239. FLEXIvarBox *ivarBox = [[FLEXIvarBox alloc] init];
  240. ivarBox.ivar = ivarList[i];
  241. [boxedIvars addObject:ivarBox];
  242. }
  243. free(ivarList);
  244. }
  245. return boxedIvars;
  246. }
  247. + (NSArray *)inheritedIvarsForClass:(Class)class
  248. {
  249. NSMutableArray *inheritedIvars = [NSMutableArray array];
  250. while ((class = [class superclass])) {
  251. [inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
  252. }
  253. return inheritedIvars;
  254. }
  255. - (void)updateFilteredIvars
  256. {
  257. NSArray *candidateIvars = self.ivars;
  258. if (self.includeInheritance) {
  259. candidateIvars = [candidateIvars arrayByAddingObjectsFromArray:self.inheritedIvars];
  260. }
  261. NSArray *unsortedFilteredIvars = nil;
  262. if ([self.filterText length] > 0) {
  263. NSMutableArray *mutableUnsortedFilteredIvars = [NSMutableArray array];
  264. for (FLEXIvarBox *ivarBox in candidateIvars) {
  265. NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  266. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  267. [mutableUnsortedFilteredIvars addObject:ivarBox];
  268. }
  269. }
  270. unsortedFilteredIvars = mutableUnsortedFilteredIvars;
  271. } else {
  272. unsortedFilteredIvars = candidateIvars;
  273. }
  274. self.filteredIvars = [unsortedFilteredIvars sortedArrayUsingComparator:^NSComparisonResult(FLEXIvarBox *ivarBox1, FLEXIvarBox *ivarBox2) {
  275. NSString *name1 = [NSString stringWithUTF8String:ivar_getName(ivarBox1.ivar)];
  276. NSString *name2 = [NSString stringWithUTF8String:ivar_getName(ivarBox2.ivar)];
  277. return [name1 caseInsensitiveCompare:name2];
  278. }];
  279. }
  280. - (NSString *)titleForIvarAtIndex:(NSInteger)index
  281. {
  282. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
  283. return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  284. }
  285. - (id)valueForIvarAtIndex:(NSInteger)index
  286. {
  287. id value = nil;
  288. if (![self objectIsClass]) {
  289. FLEXIvarBox *ivarBox = [self.filteredIvars objectAtIndex:index];
  290. value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
  291. }
  292. return value;
  293. }
  294. #pragma mark - Methods
  295. - (void)updateMethods
  296. {
  297. Class class = [self.object class];
  298. self.methods = [[self class] methodsForClass:class];
  299. self.inheritedMethods = [[self class] inheritedMethodsForClass:class];
  300. }
  301. - (void)updateFilteredMethods
  302. {
  303. self.filteredMethods = [self filteredMethodsFromMethods:self.methods inheritedMethods:self.inheritedMethods areClassMethods:NO];
  304. }
  305. - (void)updateClassMethods
  306. {
  307. const char *className = [NSStringFromClass([self.object class]) UTF8String];
  308. Class metaClass = objc_getMetaClass(className);
  309. self.classMethods = [[self class] methodsForClass:metaClass];
  310. self.inheritedClassMethods = [[self class] inheritedMethodsForClass:metaClass];
  311. }
  312. - (void)updateFilteredClassMethods
  313. {
  314. self.filteredClassMethods = [self filteredMethodsFromMethods:self.classMethods inheritedMethods:self.inheritedClassMethods areClassMethods:YES];
  315. }
  316. + (NSArray *)methodsForClass:(Class)class
  317. {
  318. NSMutableArray *boxedMethods = [NSMutableArray array];
  319. unsigned int methodCount = 0;
  320. Method *methodList = class_copyMethodList(class, &methodCount);
  321. if (methodList) {
  322. for (int i = 0; i < methodCount; i++) {
  323. FLEXMethodBox *methodBox = [[FLEXMethodBox alloc] init];
  324. methodBox.method = methodList[i];
  325. [boxedMethods addObject:methodBox];
  326. }
  327. free(methodList);
  328. }
  329. return boxedMethods;
  330. }
  331. + (NSArray *)inheritedMethodsForClass:(Class)class
  332. {
  333. NSMutableArray *inheritedMethods = [NSMutableArray array];
  334. while ((class = [class superclass])) {
  335. [inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
  336. }
  337. return inheritedMethods;
  338. }
  339. - (NSArray *)filteredMethodsFromMethods:(NSArray *)methods inheritedMethods:(NSArray *)inheritedMethods areClassMethods:(BOOL)areClassMethods
  340. {
  341. NSArray *candidateMethods = methods;
  342. if (self.includeInheritance) {
  343. candidateMethods = [candidateMethods arrayByAddingObjectsFromArray:inheritedMethods];
  344. }
  345. NSArray *unsortedFilteredMethods = nil;
  346. if ([self.filterText length] > 0) {
  347. NSMutableArray *mutableUnsortedFilteredMethods = [NSMutableArray array];
  348. for (FLEXMethodBox *methodBox in candidateMethods) {
  349. NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
  350. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  351. [mutableUnsortedFilteredMethods addObject:methodBox];
  352. }
  353. }
  354. unsortedFilteredMethods = mutableUnsortedFilteredMethods;
  355. } else {
  356. unsortedFilteredMethods = candidateMethods;
  357. }
  358. NSArray *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
  359. NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
  360. NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
  361. return [name1 caseInsensitiveCompare:name2];
  362. }];
  363. return sortedFilteredMethods;
  364. }
  365. - (NSString *)titleForMethodAtIndex:(NSInteger)index
  366. {
  367. FLEXMethodBox *methodBox = [self.filteredMethods objectAtIndex:index];
  368. return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
  369. }
  370. - (NSString *)titleForClassMethodAtIndex:(NSInteger)index
  371. {
  372. FLEXMethodBox *classMethodBox = [self.filteredClassMethods objectAtIndex:index];
  373. return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
  374. }
  375. #pragma mark - Table View Data Helpers
  376. - (NSArray *)visibleExplorerSections
  377. {
  378. NSMutableArray *visibleSections = [NSMutableArray array];
  379. NSArray *possibleSections = @[@(FLEXObjectExplorerSectionDescription),
  380. @(FLEXObjectExplorerSectionCustom),
  381. @(FLEXObjectExplorerSectionProperties),
  382. @(FLEXObjectExplorerSectionIvars),
  383. @(FLEXObjectExplorerSectionMethods),
  384. @(FLEXObjectExplorerSectionClassMethods)];
  385. for (NSNumber *possibleSection in possibleSections) {
  386. FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
  387. if ([self numberOfRowsForExplorerSection:explorerSection] > 0) {
  388. [visibleSections addObject:possibleSection];
  389. }
  390. }
  391. return visibleSections;
  392. }
  393. - (NSString *)sectionTitleWithBaseName:(NSString *)baseName totalCount:(NSUInteger)totalCount filteredCount:(NSUInteger)filteredCount
  394. {
  395. NSString *sectionTitle = nil;
  396. if (totalCount == filteredCount) {
  397. sectionTitle = [baseName stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
  398. } else {
  399. sectionTitle = [baseName stringByAppendingFormat:@" (%lu of %lu)", (unsigned long)filteredCount, (unsigned long)totalCount];
  400. }
  401. return sectionTitle;
  402. }
  403. - (FLEXObjectExplorerSection)explorerSectionAtIndex:(NSInteger)sectionIndex
  404. {
  405. return [[[self visibleExplorerSections] objectAtIndex:sectionIndex] unsignedIntegerValue];
  406. }
  407. - (NSInteger)numberOfRowsForExplorerSection:(FLEXObjectExplorerSection)section
  408. {
  409. NSInteger numberOfRows = 0;
  410. switch (section) {
  411. case FLEXObjectExplorerSectionDescription:
  412. numberOfRows = [self shouldShowDescription] ? 1 : 0;
  413. break;
  414. case FLEXObjectExplorerSectionCustom:
  415. numberOfRows = [self.customSectionVisibleIndexes count];
  416. break;
  417. case FLEXObjectExplorerSectionProperties:
  418. numberOfRows = [self.filteredProperties count];
  419. break;
  420. case FLEXObjectExplorerSectionIvars:
  421. numberOfRows = [self.filteredIvars count];
  422. break;
  423. case FLEXObjectExplorerSectionMethods:
  424. numberOfRows = [self.filteredMethods count];
  425. break;
  426. case FLEXObjectExplorerSectionClassMethods:
  427. numberOfRows = [self.filteredClassMethods count];
  428. break;
  429. }
  430. return numberOfRows;
  431. }
  432. - (NSString *)titleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  433. {
  434. NSString *title = nil;
  435. switch (section) {
  436. case FLEXObjectExplorerSectionDescription:
  437. title = [FLEXUtility safeDescriptionForObject:self.object];
  438. break;
  439. case FLEXObjectExplorerSectionCustom:
  440. title = [self customSectionTitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  441. break;
  442. case FLEXObjectExplorerSectionProperties:
  443. title = [self titleForPropertyAtIndex:row];
  444. break;
  445. case FLEXObjectExplorerSectionIvars:
  446. title = [self titleForIvarAtIndex:row];
  447. break;
  448. case FLEXObjectExplorerSectionMethods:
  449. title = [self titleForMethodAtIndex:row];
  450. break;
  451. case FLEXObjectExplorerSectionClassMethods:
  452. title = [self titleForClassMethodAtIndex:row];
  453. break;
  454. }
  455. return title;
  456. }
  457. - (NSString *)subtitleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  458. {
  459. // No subtitles for if we're showing a class rather than an instance of the class.
  460. if ([self objectIsClass]) {
  461. return nil;
  462. }
  463. NSString *subtitle = nil;
  464. switch (section) {
  465. case FLEXObjectExplorerSectionDescription:
  466. break;
  467. case FLEXObjectExplorerSectionCustom:
  468. subtitle = [self customSectionSubtitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  469. break;
  470. case FLEXObjectExplorerSectionProperties:
  471. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForPropertyAtIndex:row]];
  472. break;
  473. case FLEXObjectExplorerSectionIvars:
  474. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForIvarAtIndex:row]];
  475. break;
  476. case FLEXObjectExplorerSectionMethods:
  477. break;
  478. case FLEXObjectExplorerSectionClassMethods:
  479. break;
  480. }
  481. return subtitle;
  482. }
  483. - (BOOL)canDrillInToRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  484. {
  485. BOOL canDrillIn = NO;
  486. BOOL objectIsClass = [self objectIsClass];
  487. switch (section) {
  488. case FLEXObjectExplorerSectionDescription:
  489. break;
  490. case FLEXObjectExplorerSectionCustom:
  491. canDrillIn = [self customSectionCanDrillIntoRowWithCookie:[self customSectionRowCookieForVisibleRow:row]];
  492. break;
  493. case FLEXObjectExplorerSectionProperties: {
  494. if (!objectIsClass) {
  495. objc_property_t property = [[self.filteredProperties objectAtIndex:row] property];
  496. id currentValue = [self valueForPropertyAtIndex:row];
  497. BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue];
  498. BOOL canExplore = currentValue != nil;
  499. canDrillIn = canEdit || canExplore;
  500. }
  501. } break;
  502. case FLEXObjectExplorerSectionIvars: {
  503. if (!objectIsClass) {
  504. Ivar ivar = [[self.filteredIvars objectAtIndex:row] ivar];
  505. id currentValue = [self valueForIvarAtIndex:row];
  506. BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
  507. BOOL canExplore = currentValue != nil;
  508. canDrillIn = canEdit || canExplore;
  509. }
  510. } break;
  511. case FLEXObjectExplorerSectionMethods:
  512. canDrillIn = !objectIsClass;
  513. break;
  514. case FLEXObjectExplorerSectionClassMethods:
  515. canDrillIn = YES;
  516. break;
  517. }
  518. return canDrillIn;
  519. }
  520. - (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
  521. {
  522. NSString *title = nil;
  523. switch (section) {
  524. case FLEXObjectExplorerSectionDescription: {
  525. title = @"Description";
  526. } break;
  527. case FLEXObjectExplorerSectionCustom: {
  528. title = [self customSectionTitle];
  529. } break;
  530. case FLEXObjectExplorerSectionProperties: {
  531. NSUInteger totalCount = [self.properties count];
  532. if (self.includeInheritance) {
  533. totalCount += [self.inheritedProperties count];
  534. }
  535. title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
  536. } break;
  537. case FLEXObjectExplorerSectionIvars: {
  538. NSUInteger totalCount = [self.ivars count];
  539. if (self.includeInheritance) {
  540. totalCount += [self.inheritedIvars count];
  541. }
  542. title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
  543. } break;
  544. case FLEXObjectExplorerSectionMethods: {
  545. NSUInteger totalCount = [self.methods count];
  546. if (self.includeInheritance) {
  547. totalCount += [self.inheritedMethods count];
  548. }
  549. title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
  550. } break;
  551. case FLEXObjectExplorerSectionClassMethods: {
  552. NSUInteger totalCount = [self.classMethods count];
  553. if (self.includeInheritance) {
  554. totalCount += [self.inheritedClassMethods count];
  555. }
  556. title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
  557. } break;
  558. }
  559. return title;
  560. }
  561. - (UIViewController *)drillInViewControllerForRow:(NSUInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  562. {
  563. UIViewController *viewController = nil;
  564. switch (section) {
  565. case FLEXObjectExplorerSectionDescription:
  566. break;
  567. case FLEXObjectExplorerSectionCustom:
  568. viewController = [self customSectionDrillInViewControllerForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  569. break;
  570. case FLEXObjectExplorerSectionProperties: {
  571. objc_property_t property = [[self.filteredProperties objectAtIndex:row] property];
  572. id currentValue = [self valueForPropertyAtIndex:row];
  573. if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
  574. viewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  575. } else if (currentValue) {
  576. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  577. }
  578. } break;
  579. case FLEXObjectExplorerSectionIvars: {
  580. Ivar ivar = [[self.filteredIvars objectAtIndex:row] ivar];
  581. id currentValue = [self valueForIvarAtIndex:row];
  582. if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
  583. viewController = [[FLEXIvarEditorViewController alloc] initWithTarget:self.object ivar:ivar];
  584. } else if (currentValue) {
  585. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  586. }
  587. } break;
  588. case FLEXObjectExplorerSectionMethods: {
  589. Method method = [[self.filteredMethods objectAtIndex:row] method];
  590. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
  591. } break;
  592. case FLEXObjectExplorerSectionClassMethods: {
  593. Method method = [[self.filteredClassMethods objectAtIndex:row] method];
  594. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
  595. } break;
  596. }
  597. return viewController;
  598. }
  599. #pragma mark - Table View Data Source
  600. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  601. {
  602. return [[self visibleExplorerSections] count];
  603. }
  604. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  605. {
  606. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  607. return [self numberOfRowsForExplorerSection:explorerSection];
  608. }
  609. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  610. {
  611. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  612. return [self titleForExplorerSection:explorerSection];
  613. }
  614. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  615. {
  616. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  617. BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
  618. NSString *cellIdentifier = useDescriptionCell ? @"descriptionCell" : @"cell";
  619. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  620. if (!cell) {
  621. if (useDescriptionCell) {
  622. cell = [[FLEXDescriptionTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  623. } else {
  624. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
  625. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  626. cell.textLabel.font = cellFont;
  627. cell.detailTextLabel.font = cellFont;
  628. cell.detailTextLabel.textColor = [UIColor grayColor];
  629. }
  630. }
  631. cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  632. cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  633. cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  634. return cell;
  635. }
  636. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  637. {
  638. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  639. CGFloat height = self.tableView.rowHeight;
  640. if (explorerSection == FLEXObjectExplorerSectionDescription) {
  641. NSString *text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  642. CGFloat preferredHeight = [FLEXDescriptionTableViewCell preferredHeightWithText:text inTableViewWidth:self.tableView.frame.size.width];
  643. height = MAX(height, preferredHeight);
  644. }
  645. return height;
  646. }
  647. #pragma mark - Table View Delegate
  648. - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
  649. {
  650. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  651. return [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection];
  652. }
  653. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  654. {
  655. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  656. UIViewController *detailViewController = [self drillInViewControllerForRow:indexPath.row inExplorerSection:explorerSection];
  657. if (detailViewController) {
  658. [self.navigationController pushViewController:detailViewController animated:YES];
  659. }
  660. }
  661. #pragma mark - Custom Section
  662. - (void)updateCustomData
  663. {
  664. self.cachedCustomSectionRowCookies = [self customSectionRowCookies];
  665. }
  666. - (void)updateFilteredCustomData
  667. {
  668. NSIndexSet *filteredIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.cachedCustomSectionRowCookies count])];
  669. if ([self.filterText length] > 0) {
  670. filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
  671. BOOL matches = NO;
  672. NSString *rowTitle = [self customSectionTitleForRowCookie:[self.cachedCustomSectionRowCookies objectAtIndex:index]];
  673. if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  674. matches = YES;
  675. }
  676. return matches;
  677. }];
  678. }
  679. self.customSectionVisibleIndexes = filteredIndexSet;
  680. }
  681. - (id)customSectionRowCookieForVisibleRow:(NSUInteger)row
  682. {
  683. return [[self.cachedCustomSectionRowCookies objectsAtIndexes:self.customSectionVisibleIndexes] objectAtIndex:row];
  684. }
  685. #pragma mark - Subclasses Can Override
  686. - (NSString *)customSectionTitle
  687. {
  688. return nil;
  689. }
  690. - (NSArray *)customSectionRowCookies
  691. {
  692. return nil;
  693. }
  694. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  695. {
  696. return nil;
  697. }
  698. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  699. {
  700. return nil;
  701. }
  702. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  703. {
  704. return NO;
  705. }
  706. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  707. {
  708. return nil;
  709. }
  710. @end