| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- //
- // FLEXMetadataSection.m
- // FLEX
- //
- // Created by Tanner Bennett on 9/19/19.
- // Copyright © 2019 Flipboard. All rights reserved.
- //
- #import "FLEXMetadataSection.h"
- #import "FLEXTableView.h"
- #import "FLEXTableViewCell.h"
- #import "FLEXObjectExplorerFactory.h"
- #import "FLEXFieldEditorViewController.h"
- #import "FLEXMethodCallingViewController.h"
- #import "FLEXIvar.h"
- #import "NSArray+Functional.h"
- #import "FLEXRuntime+UIKitHelpers.h"
- @interface FLEXMetadataSection ()
- @property (nonatomic, readonly) FLEXObjectExplorer *explorer;
- /// Filtered
- @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *metadata;
- /// Unfiltered
- @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *allMetadata;
- @end
- @implementation FLEXMetadataSection
- #pragma mark - Initialization
- + (instancetype)explorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
- return [[self alloc] initWithExplorer:explorer kind:metadataKind];
- }
- - (id)initWithExplorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
- self = [super init];
- if (self) {
- _explorer = explorer;
- _metadataKind = metadataKind;
- [self reloadData];
- }
- return self;
- }
- #pragma mark - Private
- - (NSString *)titleWithBaseName:(NSString *)baseName {
- unsigned long totalCount = self.allMetadata.count;
- unsigned long filteredCount = self.metadata.count;
- if (totalCount == filteredCount) {
- return [baseName stringByAppendingFormat:@" (%lu)", totalCount];
- } else {
- return [baseName stringByAppendingFormat:@" (%lu of %lu)", filteredCount, totalCount];
- }
- }
- - (__kindof UIViewController *)detailScreenForMetadata:(id)metadata {
- id target = self.explorer.object;
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- case FLEXMetadataKindIvars: {
- // This works for both properties and ivars, we just
- // cast so the compiler knows what return type to expect since
- // this method has the same name as -[NSValue getValue:] (void *)
- id currentValue = [(FLEXIvar *)metadata getPotentiallyUnboxedValue:target];
- return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
- }
- case FLEXMetadataKindClassMethods:
- // Make sure the target is a class
- // for class methods, then fall through
- if (self.explorer.objectIsInstance && ![metadata isInstanceMethod]) {
- target = [target class];
- }
- case FLEXMetadataKindMethods: {
- return [FLEXMethodCallingViewController target:target method:metadata];
- }
- }
- }
- // Only for properties or ivars
- - (id)valueForRow:(NSInteger)row {
- id metadata = self.metadata[row];
- // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
- // because we want to "preview" what object is being stored if this is
- // a void * or something and we're given an NSValue back from getValue:
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- return [self.explorer valueForProperty:metadata];
- case FLEXMetadataKindIvars:
- return [self.explorer valueForIvar:metadata];
- // Methods: nil
- case FLEXMetadataKindMethods:
- case FLEXMetadataKindClassMethods:
- return nil;
- }
- }
- - (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
- return [self.metadata[row] suggestedAccessoryTypeWithTarget:self.explorer.object];
- }
- #pragma mark - Public
- - (void)setExcludedMetadata:(NSSet<NSString *> *)excludedMetadata {
- _excludedMetadata = excludedMetadata;
- [self reloadData];
- }
- #pragma mark - Overrides
- - (NSString *)titleForRow:(NSInteger)row {
- return [self.metadata[row] description];
- }
- - (NSString *)subtitleForRow:(NSInteger)row {
- id metadata = self.metadata[row];
- // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
- // because we want to "preview" what object is being stored if this is
- // a void * or something and we're given an NSValue back from getValue:
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- case FLEXMetadataKindIvars:
- #warning TODO: fix this logic for class properties
- if (!self.explorer.objectIsInstance) {
- if (self.metadataKind == FLEXMetadataKindProperties) {
- if (![metadata isClassProperty]) {
- return nil;
- }
- } else {
- return nil;
- }
- }
- return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
- case FLEXMetadataKindMethods:
- case FLEXMetadataKindClassMethods:
- return [metadata selectorString];
- default:
- return nil;
- }
- }
- - (NSString *)title {
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- return [self titleWithBaseName:@"Properties"];
- case FLEXMetadataKindIvars:
- return [self titleWithBaseName:@"Ivars"];
- case FLEXMetadataKindMethods:
- return [self titleWithBaseName:@"Methods"];
- case FLEXMetadataKindClassMethods:
- return [self titleWithBaseName:@"Class methods"];
- default: return nil;
- }
- }
- - (NSInteger)numberOfRows {
- return self.metadata.count;
- }
- - (void)setFilterText:(NSString *)filterText {
- super.filterText = filterText;
- if (!self.filterText.length) {
- self.metadata = self.allMetadata;
- } else {
- self.metadata = [self.allMetadata flex_filtered:^BOOL(FLEXProperty *obj, NSUInteger idx) {
- return [obj.description localizedCaseInsensitiveContainsString:self.filterText];
- }];
- }
- }
- - (void)reloadData {
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- self.allMetadata = self.explorer.properties;
- break;
- case FLEXMetadataKindIvars:
- self.allMetadata = self.explorer.ivars;
- break;
- case FLEXMetadataKindMethods:
- self.allMetadata = self.explorer.methods;
- break;
- case FLEXMetadataKindClassMethods:
- self.allMetadata = self.explorer.classMethods;
- break;
- }
- // Remove excluded metadata
- if (self.excludedMetadata.count) {
- id filterBlock = ^BOOL(id obj, NSUInteger idx) {
- return ![self.excludedMetadata containsObject:[obj name]];
- };
- // Filter exclusions and sort
- self.allMetadata = [[self.allMetadata flex_filtered:filterBlock]
- sortedArrayUsingSelector:@selector(compare:)
- ];
- }
- // Re-filter data
- self.filterText = self.filterText;
- }
- - (BOOL)canSelectRow:(NSInteger)row {
- switch (self.metadataKind) {
- case FLEXMetadataKindProperties:
- case FLEXMetadataKindIvars:
- if (![self valueForRow:row]) {
- return NO;
- }
- case FLEXMetadataKindMethods:
- return self.explorer.objectIsInstance;
- case FLEXMetadataKindClassMethods:
- return YES;
- default: return NO;
- }
- }
- - (NSString *)reuseIdentifierForRow:(NSInteger)row {
- return kFLEXDetailCell;
- }
- - (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
- return [self detailScreenForMetadata:self.metadata[row]];
- }
- - (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
- return ^(UIViewController *host) {
- [host.navigationController pushViewController:[self editorForRow:row] animated:YES];
- };
- }
- - (UIViewController *)editorForRow:(NSInteger)row {
- NSAssert(
- self.metadataKind == FLEXMetadataKindIvars ||
- self.metadataKind == FLEXMetadataKindProperties, // ||,
- // self.metadataKind == FLEXMetadataKindClassProperties,
- @"Only ivars or properties can be edited"
- );
- id metadata = self.metadata[row];
- // Nil editor means unsupported ivar or property type, or nil value
- if (self.metadataKind == FLEXMetadataKindProperties) {
- return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
- } else if (self.metadataKind == FLEXMetadataKindIvars) {
- return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
- }
- // TODO: support class properties
- @throw NSInternalInconsistencyException;
- return nil;
- }
- - (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
- cell.titleLabel.text = [self titleForRow:row];
- cell.subtitleLabel.text = [self subtitleForRow:row];
- cell.accessoryType = [self accessoryTypeForRow:row];
- }
- @end
|