// // FLEXMetadataSection.m // FLEX // // Created by Tanner Bennett on 9/19/19. // Copyright © 2020 FLEX Team. 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+FLEX.h" #import "FLEXRuntime+UIKitHelpers.h" #if TARGET_OS_TV #import "fakes.h" #endif @interface FLEXMetadataSection () @property (nonatomic, readonly) FLEXObjectExplorer *explorer; /// Filtered @property (nonatomic, copy) NSArray> *metadata; /// Unfiltered @property (nonatomic, copy) NSArray> *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]; } } - (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row { return [self.metadata[row] suggestedAccessoryTypeWithTarget:self.explorer.object]; } #pragma mark - Public - (void)setExcludedMetadata:(NSSet *)excludedMetadata { _excludedMetadata = excludedMetadata; [self reloadData]; } #pragma mark - Overrides - (NSString *)titleForRow:(NSInteger)row { return [self.metadata[row] description]; } - (NSString *)subtitleForRow:(NSInteger)row { return [self.metadata[row] previewWithTarget:self.explorer.object]; } - (NSString *)title { switch (self.metadataKind) { case FLEXMetadataKindProperties: return [self titleWithBaseName:@"Properties"]; case FLEXMetadataKindClassProperties: return [self titleWithBaseName:@"Class Properties"]; case FLEXMetadataKindIvars: return [self titleWithBaseName:@"Ivars"]; case FLEXMetadataKindMethods: return [self titleWithBaseName:@"Methods"]; case FLEXMetadataKindClassMethods: return [self titleWithBaseName:@"Class Methods"]; case FLEXMetadataKindClassHierarchy: return [self titleWithBaseName:@"Class Hierarchy"]; case FLEXMetadataKindProtocols: return [self titleWithBaseName:@"Protocols"]; case FLEXMetadataKindOther: return @"Miscellaneous"; } } - (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 FLEXMetadataKindClassProperties: self.allMetadata = self.explorer.classProperties; 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; case FLEXMetadataKindProtocols: self.allMetadata = self.explorer.conformedProtocols; break; case FLEXMetadataKindClassHierarchy: self.allMetadata = self.explorer.classHierarchy; break; case FLEXMetadataKindOther: self.allMetadata = @[self.explorer.instanceSize, self.explorer.imageName]; 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 { UITableViewCellAccessoryType accessory = [self accessoryTypeForRow:row]; #if !TARGET_OS_TV return accessory == UITableViewCellAccessoryDisclosureIndicator || accessory == UITableViewCellAccessoryDetailDisclosureButton; #else return accessory == UITableViewCellAccessoryDisclosureIndicator || accessory == TVTableViewCellAccessoryDetailDisclosureButton; #endif return accessory == UITableViewCellAccessoryDisclosureIndicator; } - (NSString *)reuseIdentifierForRow:(NSInteger)row { return [self.metadata[row] reuseIdentifierWithTarget:self.explorer.object] ?: kFLEXCodeFontCell; } - (UIViewController *)viewControllerToPushForRow:(NSInteger)row { return [self.metadata[row] viewerWithTarget:self.explorer.object]; } - (void (^)(__kindof UIViewController *))didPressInfoButtonAction:(NSInteger)row { return ^(UIViewController *host) { [host.navigationController pushViewController:[self editorForRow:row] animated:YES]; }; } - (UIViewController *)editorForRow:(NSInteger)row { return [self.metadata[row] editorWithTarget:self.explorer.object section:self]; } - (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]; } #if FLEX_AT_LEAST_IOS13_SDK - (NSString *)menuSubtitleForRow:(NSInteger)row { return [self.metadata[row] contextualSubtitleWithTarget:self.explorer.object]; } - (NSArray *)menuItemsForRow:(NSInteger)row sender:(UIViewController *)sender { NSArray *existingItems = [super menuItemsForRow:row sender:sender]; // These two metadata kinds don't any of the additional options below switch (self.metadataKind) { case FLEXMetadataKindClassHierarchy: case FLEXMetadataKindOther: return existingItems; default: break; } id metadata = self.metadata[row]; NSMutableArray *menuItems = [NSMutableArray new]; [menuItems addObject:[UIAction actionWithTitle:@"Explore Metadata" image:nil identifier:nil handler:^(__kindof UIAction *action) { [sender.navigationController pushViewController:[FLEXObjectExplorerFactory explorerViewControllerForObject:metadata ] animated:YES]; } ]]; [menuItems addObjectsFromArray:[metadata additionalActionsWithTarget:self.explorer.object sender:sender ]]; [menuItems addObjectsFromArray:existingItems]; return menuItems.copy; } - (NSArray *)copyMenuItemsForRow:(NSInteger)row { return [self.metadata[row] copiableMetadataWithTarget:self.explorer.object]; } #endif @end