diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a4ac68..765549f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,93 @@ +# 2.5.0 + +There is now a new `NSDictionary` property in `CBCNode` called metadata. It is meant to store all the information regarding an example +rather than using separate methods as previously done. With that said, we offer backwards compatibility and still allow the usage of methods to provide example information. + +Before: +``` ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Activity Indicator", @"Activity Indicator" ]; +} + ++ (NSString *)catalogDescription { + return @"Activity Indicator is a visual indication of an app loading content. It can display how " + @"long an operation will take or visualize an unspecified wait time."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + ++ (BOOL)catalogIsPresentable { + return YES; +} +``` + +After: +``` ++ (NSDictionary *)catalogMetadata { + return @{@"breadcrumbs": @[ @"Activity Indicator", @"Activity Indicator" ], + @"description": @"Activity Indicator is a visual indication of an app loading content. It can display how " + @"long an operation will take or visualize an unspecified wait time.", + @"primaryDemo": @YES, + @"presentable": @YES}; +} +``` + +## Source changes + + * [added a new metadata property that will hold all the key/values for that node. Also code refactoring (#27)](https://github.com/material-foundation/cocoapods-catalog-by-convention/commit/6c44e443e98bb87c955663c45c1245921338de1e) (Yarden Eitan) + +## API changes + +#### CBCBreadcrumbs + +*new* constant: `CBCBreadcrumbs` + +#### CBCNode + +*new* property: `metadata` in `CBCNode` + +*removed* property: `nodeDescription` in `CBCNode` + +*modified* method: `-exampleDescription` in `CBCNode` + +| Type of change: | Swift declaration | +|---|---| +| From: | `func exampleDescription() -> String` | +| To: | `func exampleDescription() -> String?` | + +*modified* method: `-exampleDescription` in `CBCNode` + +| Type of change: | Declaration | +|---|---| +| From: | `- (nonnull NSString *)exampleDescription;` | +| To: | `- (nullable NSString *)exampleDescription;` | + +#### CBCRelatedInfo + +*new* constant: `CBCRelatedInfo` + +#### CBCIsDebug + +*new* constant: `CBCIsDebug` + +#### CBCIsPresentable + +*new* constant: `CBCIsPresentable` + +#### CBCIsPrimaryDemo + +*new* constant: `CBCIsPrimaryDemo` + +#### CBCDescription + +*new* constant: `CBCDescription` + +#### CBCStoryboardName + +*new* constant: `CBCStoryboardName` + # 2.4.1 Add `exampleRelatedInfo` to the CBCNode header for external invocation. diff --git a/CatalogByConvention.podspec b/CatalogByConvention.podspec index ea31b9c..c8c6ee9 100644 --- a/CatalogByConvention.podspec +++ b/CatalogByConvention.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CatalogByConvention" - s.version = "2.4.1" + s.version = "2.5.0" s.authors = "Google Inc." s.summary = "Tools for building a Catalog by Convention." s.homepage = "https://github.com/material-foundation/cocoapods-catalog-by-convention" diff --git a/example/Podfile.lock b/example/Podfile.lock index 93973d6..809cea3 100644 --- a/example/Podfile.lock +++ b/example/Podfile.lock @@ -22,11 +22,11 @@ EXTERNAL SOURCES: :path: components/Resistor SPEC CHECKSUMS: - CatalogByConvention: 1df2d770271921f668a99245c7c4c129e78941ee - CatalogExamples: cafe3e4eae3abc948d96beb626657455c1dfb327 - CatalogUnitTests: b7a746f12abb31a905654521ee926ea007ab7275 - Resistor: 36a9ae98666be3b4f34d8133fad442fa87fdbce2 + CatalogByConvention: f4b95f8905470807a5022eabd1d3d9ce07f6a66f + CatalogExamples: 7a95e6ea7befbd43c5ceb1427a9b161f6d8fc34e + CatalogUnitTests: 2fbf7f2e894dd3777f11573a7a5314adb1e4fad7 + Resistor: a17e39cab5f42993c2b3ede22ce3829b707a9ac8 PODFILE CHECKSUM: bb59c09c71f8777bbe79af5ae920e3d58849ab41 -COCOAPODS: 1.3.1 +COCOAPODS: 1.4.0 diff --git a/src/CBCCatalogExample.h b/src/CBCCatalogExample.h index 31deb60..7e3000c 100644 --- a/src/CBCCatalogExample.h +++ b/src/CBCCatalogExample.h @@ -25,35 +25,47 @@ */ @protocol CBCCatalogExample +/** + Returns a dictionary with metaata information for the example. + */ ++ (nonnull NSDictionary *)catalogMetadata; + +@optional + /** Return a list of breadcrumbs defining the navigation path taken to reach this example. */ -+ (nonnull NSArray *)catalogBreadcrumbs; ++ (nonnull NSArray *)catalogBreadcrumbs + __attribute__((deprecated("use catalogMetadata[CBCBreadcrumbs] instead."))); /** Return a BOOL stating whether this example should be treated as the primary demo of the component. */ -+ (BOOL)catalogIsPrimaryDemo; ++ (BOOL)catalogIsPrimaryDemo + __attribute__((deprecated("use catalogMetadata[CBCIsPrimaryDemo] instead.")));; /** Return a BOOL stating whether this example is presentable and should be part of the catalog app. */ -+ (BOOL)catalogIsPresentable; ++ (BOOL)catalogIsPresentable + __attribute__((deprecated("use catalogMetadata[CBCIsPresentable] instead."))); /** Return a BOOL stating whether this example is in debug mode and should appear as the initial view controller. */ -+ (BOOL)catalogIsDebug; - -@optional ++ (BOOL)catalogIsDebug + __attribute__((deprecated("use catalogMetadata[CBCIsDebug] instead."))); /** Return the name of a UIStoryboard from which the example's view controller should be instantiated. */ -- (nonnull NSString *)catalogStoryboardName; +- (nonnull NSString *)catalogStoryboardName + __attribute__((deprecated("use catalogMetadata[CBCStoryboardName] instead."))); /** Return a description of the example. */ -- (nonnull NSString *)catalogDescription; +- (nonnull NSString *)catalogDescription + __attribute__((deprecated("use catalogMetadata[CBCDescription] instead."))); /** Return a link to related information or resources. */ -- (nonnull NSURL *)catalogRelatedInfo; +- (nonnull NSURL *)catalogRelatedInfo + __attribute__((deprecated("use catalogMetadata[CBCRelatedInfo] instead."))); @end diff --git a/src/CBCNodeListViewController.h b/src/CBCNodeListViewController.h index f0a4028..c510a32 100644 --- a/src/CBCNodeListViewController.h +++ b/src/CBCNodeListViewController.h @@ -16,6 +16,21 @@ #import +/** This key represents a strings array of the breadcrumbs showing the hierarchy of the example */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCBreadcrumbs; +/** This key represents a boolean value if the example is for debugging */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCIsDebug; +/** This key represents a string for the description for the example */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCDescription; +/** This key represents a boolean value if to present the example in the Catalog app or not */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCIsPresentable; +/** This key represents a boolean value if the example is the primary demo */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCIsPrimaryDemo; +/** This key represents an NSURL value providing related info for the example */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCRelatedInfo; +/** This key represents a string value of the storyboard name for the example */ +FOUNDATION_EXTERN NSString *_Nonnull const CBCStoryboardName; + @class CBCNode; /** @@ -69,9 +84,6 @@ FOUNDATION_EXTERN CBCNode *_Nonnull CBCCreatePresentableNavigationTree(void); /** The title for this node. */ @property(nonatomic, copy, nonnull, readonly) NSString *title; -/** The description for this node. */ -@property(nonatomic, copy, nonnull, readonly) NSString *nodeDescription; - /** The children of this node. */ @property(nonatomic, strong, nonnull) NSArray *children; @@ -83,6 +95,13 @@ FOUNDATION_EXTERN CBCNode *_Nonnull CBCCreatePresentableNavigationTree(void); */ @property(nonatomic, strong, nullable) CBCNode *debugLeaf; +/** + This NSDictionary holds all the metadata related to this CBCNode. + If it is an example noe, a primary demo, related info, + if presentable in Catalog, etc. + */ +@property(nonatomic, strong, nonnull) NSDictionary *metadata; + /** Returns YES if this is an example node. */ - (BOOL)isExample; @@ -111,7 +130,7 @@ FOUNDATION_EXTERN CBCNode *_Nonnull CBCCreatePresentableNavigationTree(void); Check that isExample returns YES before invoking. */ -- (nonnull NSString *)exampleDescription; +- (nullable NSString *)exampleDescription; /** Returns a link to related information for the example. */ - (nullable NSURL *)exampleRelatedInfo; diff --git a/src/CBCNodeListViewController.m b/src/CBCNodeListViewController.m index e7f5f84..d4553e7 100644 --- a/src/CBCNodeListViewController.m +++ b/src/CBCNodeListViewController.m @@ -19,22 +19,21 @@ #import "CBCCatalogExample.h" #import "private/CBCRuntime.h" -void CBCAddNodeFromBreadCrumbs(CBCNode *tree, NSArray *breadCrumbs, Class aClass); +@interface CBCNode() +@property(nonatomic, strong, nullable) NSMutableDictionary *map; +@property(nonatomic, strong, nullable) Class exampleClass; +@end @implementation CBCNode { - NSMutableDictionary *_map; NSMutableArray *_children; - Class _exampleClass; - BOOL _isPresentable; } - (instancetype)initWithTitle:(NSString *)title { self = [super init]; if (self) { _title = [title copy]; - _map = [NSMutableDictionary dictionary]; + self.map = [NSMutableDictionary dictionary]; _children = [NSMutableArray array]; - _isPresentable = NO; CBCFixViewDebuggingIfNeeded(); } return self; @@ -45,22 +44,10 @@ - (NSComparisonResult)compare:(CBCNode *)otherObject { } - (void)addChild:(CBCNode *)child { - _map[child.title] = child; + self.map[child.title] = child; [_children addObject:child]; } -- (NSDictionary *)map { - return _map; -} - -- (void)setExampleClass:(Class)exampleClass { - _exampleClass = exampleClass; -} - -- (void)setIsPresentable:(Class)exampleClass { - _isPresentable = CBCCatalogIsPresentableFromClass(exampleClass); -} - - (void)finalizeNode { _children = [[_children sortedArrayUsingSelector:@selector(compare:)] mutableCopy]; } @@ -68,34 +55,49 @@ - (void)finalizeNode { #pragma mark Public - (BOOL)isExample { - return _exampleClass != nil; + return self.exampleClass != nil; } - (NSString *)exampleViewControllerName { + NSAssert(self.exampleClass != nil, @"This node has no associated example."); return NSStringFromClass(_exampleClass); } - (UIViewController *)createExampleViewController { - NSAssert(_exampleClass != nil, @"This node has no associated example."); - return CBCViewControllerFromClass(_exampleClass); + NSAssert(self.exampleClass != nil, @"This node has no associated example."); + return CBCViewControllerFromClass(self.exampleClass, self.metadata); } - (NSString *)exampleDescription { - NSAssert(_exampleClass != nil, @"This node has no associated example."); - return CBCDescriptionFromClass(_exampleClass); + NSString *description = [self.metadata objectForKey:CBCDescription]; + if (description != nil && [description isKindOfClass:[NSString class]]) { + return description; + } + return nil; } - (NSURL *)exampleRelatedInfo { - NSAssert(_exampleClass != nil, @"This node has no associated example."); - return CBCRelatedInfoFromClass(_exampleClass); + NSURL *relatedInfo = [self.metadata objectForKey:CBCRelatedInfo]; + if (relatedInfo != nil && [relatedInfo isKindOfClass:[NSURL class]]) { + return relatedInfo; + } + return nil; } - (BOOL)isPrimaryDemo { - return CBCCatalogIsPrimaryDemoFromClass(_exampleClass); + id isPrimaryDemo; + if ((isPrimaryDemo = [self.metadata objectForKey:CBCIsPrimaryDemo]) != nil) { + return [isPrimaryDemo boolValue]; + } + return NO; } - (BOOL)isPresentable { - return _isPresentable; + id isPresentable; + if ((isPresentable = [self.metadata objectForKey:CBCIsPresentable]) != nil) { + return [isPresentable boolValue]; + } + return NO; } @end @@ -103,14 +105,14 @@ - (BOOL)isPresentable { @implementation CBCNodeListViewController - (instancetype)initWithNode:(CBCNode *)node { - NSAssert(!_node.isExample, @"%@ cannot represent example nodes.", + NSAssert(!self.node.isExample, @"%@ cannot represent example nodes.", NSStringFromClass([self class])); self = [super initWithNibName:nil bundle:nil]; if (self) { _node = node; - self.title = _node.title; + self.title = self.node.title; } return self; } @@ -154,7 +156,7 @@ - (void)viewDidAppear:(BOOL)animated { #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return (NSInteger)[_node.children count]; + return (NSInteger)[self.node.children count]; } - (UITableViewCell *)tableView:(UITableView *)tableView @@ -164,7 +166,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } - cell.textLabel.text = [_node.children[(NSUInteger)indexPath.row] title]; + cell.textLabel.text = [self.node.children[(NSUInteger)indexPath.row] title]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } @@ -172,7 +174,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - CBCNode *node = _node.children[(NSUInteger)indexPath.row]; + CBCNode *node = self.node.children[(NSUInteger)indexPath.row]; UIViewController *viewController = nil; if ([node isExample]) { viewController = [node createExampleViewController]; @@ -184,30 +186,61 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath @end -static CBCNode *CBCCreateTreeWithOnlyPresentable(BOOL onlyPresentable) { - NSArray *allClasses = CBCGetAllClasses(); - NSArray *breadcrumbClasses = CBCClassesRespondingToSelector(allClasses, - @selector(catalogBreadcrumbs)); - NSArray *classes; - if (onlyPresentable) { - classes = [breadcrumbClasses filteredArrayUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { - return CBCCatalogIsPresentableFromClass(object); - }]]; - } else { - classes = breadcrumbClasses; +static void CBCAddNodeFromBreadCrumbs(CBCNode *tree, + NSArray *breadCrumbs, + Class aClass, + NSDictionary *metadata) { + // Walk down the navigation tree one breadcrumb at a time, creating nodes along the way. + + CBCNode *node = tree; + for (NSUInteger ix = 0; ix < [breadCrumbs count]; ++ix) { + NSString *title = breadCrumbs[ix]; + BOOL isLastCrumb = ix == [breadCrumbs count] - 1; + + // Don't walk the last crumb + if (node.map[title] && !isLastCrumb) { + node = node.map[title]; + continue; + } + + CBCNode *child = [[CBCNode alloc] initWithTitle:title]; + [node addChild:child]; + child.metadata = metadata; + if ([[child.metadata objectForKey:CBCIsPrimaryDemo] boolValue] == YES) { + node.metadata = child.metadata; + } + if ([[child.metadata objectForKey:CBCIsDebug] boolValue] == YES) { + tree.debugLeaf = child; + } + node = child; } - CBCNode *tree = [[CBCNode alloc] initWithTitle:@"Root"]; - for (Class aClass in classes) { - // Each example view controller defines its own "breadcrumbs". - NSArray *breadCrumbs = CBCCatalogBreadcrumbsFromClass(aClass); + node.exampleClass = aClass; +} + +static CBCNode *CBCCreateTreeWithOnlyPresentable(BOOL onlyPresentable) { + NSArray *allClasses = CBCGetAllCompatibleClasses(); + NSArray *filteredClasses = [allClasses filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { + NSDictionary *metadata = CBCCatalogMetadataFromClass(object); + id breadcrumbs = [metadata objectForKey:CBCBreadcrumbs]; + BOOL validObject = breadcrumbs != nil && [breadcrumbs isKindOfClass:[NSArray class]]; + if (onlyPresentable) { + validObject &= ([[metadata objectForKey:CBCIsPresentable] boolValue] == YES); + } + return validObject; + }]]; + CBCNode *tree = [[CBCNode alloc] initWithTitle:@"Root"]; + for (Class aClass in filteredClasses) { + // Each example view controller defines its own breadcrumbs (metadata[CBCBreadcrumbs]). + NSDictionary *metadata = CBCCatalogMetadataFromClass(aClass); + NSArray *breadCrumbs = [metadata objectForKey:CBCBreadcrumbs]; if ([[breadCrumbs firstObject] isKindOfClass:[NSString class]]) { - CBCAddNodeFromBreadCrumbs(tree, breadCrumbs, aClass); + CBCAddNodeFromBreadCrumbs(tree, breadCrumbs, aClass, metadata); } else if ([[breadCrumbs firstObject] isKindOfClass:[NSArray class]]) { for (NSArray *parallelBreadCrumb in breadCrumbs) { - CBCAddNodeFromBreadCrumbs(tree, parallelBreadCrumb, aClass); + CBCAddNodeFromBreadCrumbs(tree, parallelBreadCrumb, aClass, metadata); } } } @@ -232,30 +265,3 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath CBCNode *CBCCreatePresentableNavigationTree(void) { return CBCCreateTreeWithOnlyPresentable(YES); } - -void CBCAddNodeFromBreadCrumbs(CBCNode *tree, NSArray *breadCrumbs, Class aClass) { - // Walk down the navigation tree one breadcrumb at a time, creating nodes along the way. - - CBCNode *node = tree; - for (NSUInteger ix = 0; ix < [breadCrumbs count]; ++ix) { - NSString *title = breadCrumbs[ix]; - BOOL isLastCrumb = ix == [breadCrumbs count] - 1; - - // Don't walk the last crumb - - if (node.map[title] && !isLastCrumb) { - node = node.map[title]; - continue; - } - - CBCNode *child = [[CBCNode alloc] initWithTitle:title]; - [node addChild:child]; - [node setIsPresentable:aClass]; - if (CBCCatalogIsDebugLeaf(aClass)) { - tree.debugLeaf = child; - } - node = child; - } - - node.exampleClass = aClass; -} diff --git a/src/private/CBCRuntime.h b/src/private/CBCRuntime.h index aad0663..085755d 100644 --- a/src/private/CBCRuntime.h +++ b/src/private/CBCRuntime.h @@ -17,24 +17,15 @@ #import #import -#pragma mark Breadcrumb retrieval +#pragma mark Class invocations -/** Invokes +catalogBreadcrumbs on the class and returns the corresponding array of strings. */ -FOUNDATION_EXTERN NSArray *CBCCatalogBreadcrumbsFromClass(Class aClass); - -/** Invokes +catalogIsPrimaryDemo on the class and returns the BOOL value. */ -FOUNDATION_EXTERN BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass); - -/** Invokes +catalogIsPresentable on the class and returns the BOOL value. */ -FOUNDATION_EXTERN BOOL CBCCatalogIsPresentableFromClass(Class aClass); - -/** Invokes +catalogIsDebug on the class and returns the BOOL value. */ -FOUNDATION_EXTERN BOOL CBCCatalogIsDebugLeaf(Class aClass); +/** Invokes +catalogMetadata on the class and returns the NSDictionary value */ +FOUNDATION_EXTERN NSDictionary *CBCCatalogMetadataFromClass(Class aClass); #pragma mark Runtime enumeration /** Returns all Objective-C and Swift classes available to the runtime. */ -FOUNDATION_EXTERN NSArray *CBCGetAllClasses(void); +FOUNDATION_EXTERN NSArray *CBCGetAllCompatibleClasses(void); /** Returns an array of classes that respond to a given static method selector. */ FOUNDATION_EXTERN NSArray *CBCClassesRespondingToSelector(NSArray *classes, @@ -55,13 +46,7 @@ void CBCCatalogInvokeFromClassAndSelector(Class aClass, SEL selector, void *retV be created with the returned name. The returned view controller will be instantiated by invoking -instantiateInitialViewController on the UIStoryboard instance. */ -FOUNDATION_EXTERN UIViewController *CBCViewControllerFromClass(Class aClass); - -/** Create a description from the provided class. **/ -FOUNDATION_EXTERN NSString *CBCDescriptionFromClass(Class aClass); - -/** Create a link to related information from the provided class. **/ -FOUNDATION_EXTERN NSURL *CBCRelatedInfoFromClass(Class aClass); +FOUNDATION_EXTERN UIViewController *CBCViewControllerFromClass(Class aClass, NSDictionary *metadata); #pragma mark Fix View Debugging diff --git a/src/private/CBCRuntime.m b/src/private/CBCRuntime.m index 738748a..39e3fee 100644 --- a/src/private/CBCRuntime.m +++ b/src/private/CBCRuntime.m @@ -15,58 +15,112 @@ */ #import "CBCRuntime.h" - #import "CBCCatalogExample.h" - #import -#pragma mark Breadcrumb retrieval +#pragma mark Metadata keys -NSArray *CBCCatalogBreadcrumbsFromClass(Class aClass) { - return [aClass performSelector:@selector(catalogBreadcrumbs)]; -} +NSString *const CBCBreadcrumbs = @"breadcrumbs"; +NSString *const CBCIsDebug = @"debug"; +NSString *const CBCDescription = @"description"; +NSString *const CBCIsPresentable = @"presentable"; +NSString *const CBCIsPrimaryDemo = @"primaryDemo"; +NSString *const CBCRelatedInfo = @"relatedInfo"; +NSString *const CBCStoryboardName = @"storyboardName"; -#pragma mark Primary demo check - -void CBCCatalogInvokeFromClassAndSelector(Class aClass, SEL selector, void *retValue) { - if ([aClass respondsToSelector:selector]) { - NSMethodSignature *signature = - [aClass methodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - invocation.selector = selector; - invocation.target = aClass; - [invocation invoke]; - [invocation getReturnValue:retValue]; - } +#pragma mark Class invocations + +static NSArray *CBCCatalogBreadcrumbsFromClass(Class aClass) { + return [aClass performSelector:@selector(catalogBreadcrumbs)]; } -BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass) { +static BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass) { BOOL isPrimary = NO; - CBCCatalogInvokeFromClassAndSelector(aClass, - @selector(catalogIsPrimaryDemo), - &isPrimary); + if ([aClass respondsToSelector:@selector(catalogIsPrimaryDemo)]) { + isPrimary = [aClass catalogIsPrimaryDemo]; + } return isPrimary; } -BOOL CBCCatalogIsPresentableFromClass(Class aClass) { +static BOOL CBCCatalogIsPresentableFromClass(Class aClass) { BOOL isPresentable = NO; - CBCCatalogInvokeFromClassAndSelector(aClass, - @selector(catalogIsPresentable), - &isPresentable); + if ([aClass respondsToSelector:@selector(catalogIsPresentable)]) { + isPresentable = [aClass catalogIsPresentable]; + } return isPresentable; } -BOOL CBCCatalogIsDebugLeaf(Class aClass) { +static BOOL CBCCatalogIsDebugLeaf(Class aClass) { BOOL isDebugLeaf = NO; - CBCCatalogInvokeFromClassAndSelector(aClass, - @selector(catalogIsDebug), - &isDebugLeaf); + if ([aClass respondsToSelector:@selector(catalogIsDebug)]) { + isDebugLeaf = [aClass catalogIsDebug]; + } return isDebugLeaf; } +static NSURL *CBCRelatedInfoFromClass(Class aClass) { + NSURL *catalogRelatedInfo = nil; + if ([aClass respondsToSelector:@selector(catalogRelatedInfo)]) { + catalogRelatedInfo = [aClass catalogRelatedInfo]; + } + return catalogRelatedInfo; +} + +static NSString *CBCDescriptionFromClass(Class aClass) { + NSString *catalogDescription = nil; + if ([aClass respondsToSelector:@selector(catalogDescription)]) { + catalogDescription = [aClass catalogDescription]; + } + return catalogDescription; +} + +static NSString *CBCStoryboardNameFromClass(Class aClass) { + NSString *catalogStoryboardName = nil; + if ([aClass respondsToSelector:@selector(catalogStoryboardName)]) { + catalogStoryboardName = [aClass catalogStoryboardName]; + } + return catalogStoryboardName; +} + +static NSDictionary *CBCConstructMetadataFromMethods(Class aClass) { + NSMutableDictionary *catalogMetadata = [NSMutableDictionary new]; + if ([aClass respondsToSelector:@selector(catalogBreadcrumbs)]) { + [catalogMetadata setObject:CBCCatalogBreadcrumbsFromClass(aClass) forKey:CBCBreadcrumbs]; + [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsPrimaryDemoFromClass(aClass)] + forKey:CBCIsPrimaryDemo]; + [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsPresentableFromClass(aClass)] + forKey:CBCIsPresentable]; + [catalogMetadata setObject:[NSNumber numberWithBool:CBCCatalogIsDebugLeaf(aClass)] + forKey:CBCIsDebug]; + NSURL *relatedInfo; + if ((relatedInfo = CBCRelatedInfoFromClass(aClass)) != nil) { + [catalogMetadata setObject:CBCRelatedInfoFromClass(aClass) forKey:CBCRelatedInfo]; + } + NSString *description; + if ((description = CBCDescriptionFromClass(aClass)) != nil) { + [catalogMetadata setObject:CBCDescriptionFromClass(aClass) forKey:CBCDescription]; + } + NSString *storyboardName; + if ((storyboardName = CBCStoryboardNameFromClass(aClass)) != nil) { + [catalogMetadata setObject:CBCStoryboardNameFromClass(aClass) forKey:CBCStoryboardName]; + } + } + return catalogMetadata; +} + +NSDictionary *CBCCatalogMetadataFromClass(Class aClass) { + NSDictionary *catalogMetadata; + if ([aClass respondsToSelector:@selector(catalogMetadata)]) { + catalogMetadata = [aClass catalogMetadata]; + } else { + catalogMetadata = CBCConstructMetadataFromMethods(aClass); + } + return catalogMetadata; +} + #pragma mark Runtime enumeration -NSArray *CBCGetAllClasses(void) { +NSArray *CBCGetAllCompatibleClasses(void) { int numberOfClasses = objc_getClassList(NULL, 0); Class *classList = (Class *)malloc((size_t)numberOfClasses * sizeof(Class)); objc_getClassList(classList, numberOfClasses); @@ -117,9 +171,9 @@ BOOL CBCCatalogIsDebugLeaf(Class aClass) { #pragma mark UIViewController instantiation -UIViewController *CBCViewControllerFromClass(Class aClass) { - if ([aClass respondsToSelector:@selector(catalogStoryboardName)]) { - NSString *storyboardName = [aClass catalogStoryboardName]; +UIViewController *CBCViewControllerFromClass(Class aClass, NSDictionary *metadata) { + if ([metadata objectForKey:CBCStoryboardName]) { + NSString *storyboardName = [metadata objectForKey:CBCStoryboardName]; NSBundle *bundle = [NSBundle bundleForClass:aClass]; UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; NSCAssert(storyboard, @"expecting a storyboard to exist at %@", storyboardName); @@ -130,22 +184,6 @@ BOOL CBCCatalogIsDebugLeaf(Class aClass) { return [[aClass alloc] init]; } -NSString *CBCDescriptionFromClass(Class aClass) { - if ([aClass respondsToSelector:@selector(catalogDescription)]) { - NSString *catalogDescription = [aClass catalogDescription]; - return catalogDescription; - } - return nil; -} - -NSURL *CBCRelatedInfoFromClass(Class aClass) { - if ([aClass respondsToSelector:@selector(catalogRelatedInfo)]) { - NSURL *catalogRelatedInfo = [aClass catalogRelatedInfo]; - return catalogRelatedInfo; - } - return nil; -} - #pragma mark Fix View Debugging void CBCFixViewDebuggingIfNeeded(void) {