From 880afd90496872b0e38cf109ad2530b656f9dfbc Mon Sep 17 00:00:00 2001 From: OY Date: Tue, 1 Apr 2014 23:21:39 -0700 Subject: [PATCH 01/13] Added Uber Mode which adds class methods and strongly typed return values. --- CodeGenTestApp/Base.lproj/Main.storyboard | 20 +- CodeGenTestApp/CGTAMasterViewController.h | 5 + CodeGenTestApp/CGTAMasterViewController.m | 83 ++- .../CodeGenTestApp.xcodeproj/project.pbxproj | 12 +- CodeGenTestApp/UberMode.storyboard | 479 ++++++++++++++++++ Shared/CGUCodeGenTool.h | 4 + Shared/CGUCodeGenTool.m | 38 +- identifierconstants/IDStoryboardDumper.m | 245 ++++++++- 8 files changed, 845 insertions(+), 41 deletions(-) create mode 100644 CodeGenTestApp/UberMode.storyboard diff --git a/CodeGenTestApp/Base.lproj/Main.storyboard b/CodeGenTestApp/Base.lproj/Main.storyboard index 5e99825..b8de50c 100644 --- a/CodeGenTestApp/Base.lproj/Main.storyboard +++ b/CodeGenTestApp/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -51,14 +51,24 @@ + + + + + - + + + + + + @@ -71,7 +81,7 @@ - + @@ -126,4 +136,4 @@ - \ No newline at end of file + diff --git a/CodeGenTestApp/CGTAMasterViewController.h b/CodeGenTestApp/CGTAMasterViewController.h index 63a5eab..7339131 100644 --- a/CodeGenTestApp/CGTAMasterViewController.h +++ b/CodeGenTestApp/CGTAMasterViewController.h @@ -7,6 +7,11 @@ // See the LICENSE file distributed with this work for the terms under // which Square, Inc. licenses this file to you. +@interface CGTAFlagCollectionViewCell : UICollectionViewCell + +@property (nonatomic, weak) IBOutlet UIImageView *imageView; + +@end @interface CGTAMasterViewController : UICollectionViewController @end diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index d1eb804..e555f71 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -12,12 +12,10 @@ #import "CGTAImagesCatalog+RuntimeHackery.h" #import "CGTAMainStoryboardIdentifiers.h" - -@interface CGTAFlagCollectionViewCell : UICollectionViewCell - -@property (nonatomic, weak) IBOutlet UIImageView *imageView; - -@end +// To disable uber mode: +// 1. Comment out the "#define UBER_MODE" below +// 2. Go to the target's build phases settings, and remove the -u option from the objc-identifierconstants command +#define UBER_MODE @interface CGTAMasterViewController () @@ -27,7 +25,6 @@ @interface CGTAMasterViewController () @end - @implementation CGTAMasterViewController #pragma mark - NSObject @@ -41,10 +38,76 @@ - (void)awakeFromNib; - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { +#ifndef UBER_MODE + // New version: get the properly compiler-checked spelling from the storyboard. if ([segue.identifier isEqualToString:CGTAMainStoryboardTapOnFlagIdentifier]) { CGTADetailViewController *detailViewController = segue.destinationViewController; - detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image; + detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; + } +#else + // But really, why not use class methods? + // ... here we are guaranteed that topOnFlag is our own view controller's segue and not some random one in the storyboard + if ([segue.identifier isEqualToString:[self tapOnFlagSegueIdentifier]]) { + CGTADetailViewController *detailViewController = segue.destinationViewController; + detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; } +#endif +} + +- (IBAction)pushTapped:(id)sender +{ + CGTADetailViewController *detailViewController = nil; + +#ifndef UBER_MODE + UIStoryboard *storyboard = nil; + + // Initial version: full of strings that you have to type correctly! + // Misspell any of these and your app will not work as expected. + storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; + detailViewController = [storyboard instantiateViewControllerWithIdentifier:@"Detail View Controller"]; + + // New version: get the properly compiler-checked spelling from the storyboard. + storyboard = [UIStoryboard storyboardWithName:CGTAMainStoryboardName bundle:nil]; + // ... there is no guarantee that the storyboard actually has an identifier named CGTAMainStoryboardDetailViewControllerIdentifier. We are using two different constants that we must manually guarantee are in sync. + detailViewController = [storyboard instantiateViewControllerWithIdentifier:CGTAMainStoryboardDetailViewControllerIdentifier]; + + detailViewController.image = [CGTAImagesCatalog usaImage]; + // ... also, we have no guarantee that this view controller is an instance of CGTADetailViewController, thus accessing the .image property may case another error. + [self.navigationController pushViewController:detailViewController animated:YES]; +#else + +#if 0 + // Here is example of a crash that might happen (especially if we reorganize our storyboards): + storyboard = [UIStoryboard storyboardWithName:CGTAMainStoryboardName bundle:nil]; + detailViewController = [storyboard instantiateViewControllerWithIdentifier:CGTAUberModeStoryboardDetailViewControllerIdentifier]; + // the above will crash because of the discrepency between cgtaMAINstoryboardname and cgtaUBERMODEstoryboarddetailveiwcontrolleridentifier + // the compiler will never catch these kind of mistakes. Unless we use Uber Mode... +#endif + + // Then really, why not use class methods? + detailViewController = [CGTAMainStoryboard instantiateDetailViewController]; + // ... two lines became one, guaranteeing the previous error will never happen, + + detailViewController.image = [CGTAImagesCatalog usaImage]; + // ... also notice how this returns a CGTADetailViewController, rather than an id, so we can be assured that .image is a valid property! + [self.navigationController pushViewController:detailViewController animated:YES]; +#endif +} + +- (IBAction)performTapped:(id)sender +{ +#ifndef UBER_MODE + // Initial version: full of strings that you have to type correctly! + // Misspell any of these and your app will not work as expected. + //[self performSegueWithIdentifier:@"Tap on Flag" sender:nil]; + + // New version: get the properly compiler-checked spelling from the storyboard. + [self performSegueWithIdentifier:CGTAMainStoryboardTapOnFlagIdentifier sender:nil]; +#else + + // But really, why not use class methods? + [self performTapOnFlagSegue]; +#endif } #pragma mark - Private methods @@ -85,7 +148,11 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; { +#ifndef UBER_MODE CGTAFlagCollectionViewCell *cell = (CGTAFlagCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CGTAMainStoryboardImageCellIdentifier forIndexPath:indexPath]; +#else + CGTAFlagCollectionViewCell *cell = [self dequeueImageCellForIndexPath:indexPath ofCollectionView:collectionView]; +#endif cell.imageView.image = self.flagImages[indexPath.item]; return cell; } diff --git a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj index 8f5de75..95e82eb 100644 --- a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj +++ b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */ = {isa = PBXBuildFile; fileRef = A838793518A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.m */; }; A881854218A9B622002803FC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A83878E518A0367C00B386D6 /* main.m */; }; A881854418A9B663002803FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A83878EB18A0367C00B386D6 /* Main.storyboard */; }; + AA24EC5A18EB4F8E00DB0F94 /* UberMode.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */; }; + AA24EC5D18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -95,6 +97,9 @@ A881852518A9B512002803FC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; A881852718A9B520002803FC /* codegenutils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = codegenutils.xcodeproj; path = ../codegenutils.xcodeproj; sourceTree = ""; }; A89D8FE617CFFDCE0077F2B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UberMode.storyboard; sourceTree = ""; }; + AA24EC5B18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAUberModeStoryboardIdentifiers.h; sourceTree = ""; }; + AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAUberModeStoryboardIdentifiers.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -117,6 +122,7 @@ A83878E818A0367C00B386D6 /* CGTAAppDelegate.h */, A83878E918A0367C00B386D6 /* CGTAAppDelegate.m */, A83878EB18A0367C00B386D6 /* Main.storyboard */, + AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */, A83878F418A0367C00B386D6 /* Images.xcassets */, A83878EE18A0367C00B386D6 /* CGTAMasterViewController.h */, A83878EF18A0367C00B386D6 /* CGTAMasterViewController.m */, @@ -146,6 +152,8 @@ A838791518A0455E00B386D6 /* CGTAImagesCatalog.m */, A838793118A0557E00B386D6 /* CGTAMainStoryboardIdentifiers.h */, A838793218A0557E00B386D6 /* CGTAMainStoryboardIdentifiers.m */, + AA24EC5B18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.h */, + AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */, A838791818A04AB300B386D6 /* CGTATestAppColorList.h */, A838791918A04AB300B386D6 /* CGTATestAppColorList.m */, A838793418A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.h */, @@ -280,6 +288,7 @@ buildActionMask = 2147483647; files = ( A881854418A9B663002803FC /* Main.storyboard in Resources */, + AA24EC5A18EB4F8E00DB0F94 /* UberMode.storyboard in Resources */, A881853A18A9B614002803FC /* Images.xcassets in Resources */, A83878E418A0367C00B386D6 /* InfoPlist.strings in Resources */, ); @@ -300,7 +309,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "DERIVED_SOURCES=\"$SRCROOT/Derived Sources\"\nTOOL_PATH=$BUILD_ROOT/$CONFIGURATION\n$TOOL_PATH/objc-assetgen -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-colordump -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-identifierconstants -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n"; + shellScript = "DERIVED_SOURCES=\"$SRCROOT/Derived Sources\"\nTOOL_PATH=$BUILD_ROOT/$CONFIGURATION\n$TOOL_PATH/objc-assetgen -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-colordump -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-identifierconstants -u -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -313,6 +322,7 @@ A881853C18A9B622002803FC /* CGTAMasterViewController.m in Sources */, A881853F18A9B622002803FC /* CGTAMainStoryboardIdentifiers.m in Sources */, A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */, + AA24EC5D18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m in Sources */, A881853E18A9B622002803FC /* CGTAImagesCatalog.m in Sources */, A881854018A9B622002803FC /* CGTATestAppColorList.m in Sources */, A881854218A9B622002803FC /* main.m in Sources */, diff --git a/CodeGenTestApp/UberMode.storyboard b/CodeGenTestApp/UberMode.storyboard new file mode 100644 index 0000000..78e11a5 --- /dev/null +++ b/CodeGenTestApp/UberMode.storyboard @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index 75c003a..6913c43 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -18,11 +18,15 @@ @property (copy) NSURL *inputURL; @property (copy) NSString *classPrefix; +@property (copy) NSString *searchPath; @property BOOL targetiOS6; @property BOOL skipClassDeclaration; +@property BOOL uberMode; @property (copy) NSString *className; @property (strong) NSMutableArray *interfaceContents; +/// An array of strings such as "" which will be imported at the top of the .h file. +@property (strong) NSMutableArray *interfaceImports; @property (strong) NSMutableArray *implementationContents; - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 07f07a0..4959ef1 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -31,20 +31,23 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; { char opt = -1; NSURL *searchURL = nil; + NSString *searchPath = nil; NSString *classPrefix = @""; BOOL target6 = NO; + BOOL uberMode = NO; NSMutableArray *inputURLs = [NSMutableArray array]; - while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6")) != -1) { + while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6u")) != -1) { switch (opt) { case 'h': { - printf("Usage: %s [-6] [-o ] [-f ] [-p ] []\n", basename((char *)argv[0])); + printf("Usage: %s [-6] [-u] [-o ] [-f ] [-p ] []\n", basename((char *)argv[0])); printf(" %s -h\n\n", basename((char *)argv[0])); printf("Options:\n"); printf(" -6 Target iOS 6 in addition to iOS 7\n"); printf(" -o Output files at \n"); printf(" -f Search for *.%s folders starting from \n", [[self inputFileExtension] UTF8String]); printf(" -p Use as the class prefix in the generated code\n"); + printf(" -u Uber mode\n"); printf(" -h Print this help and exit\n"); printf(" Input files; this and/or -f are required.\n"); return 0; @@ -58,7 +61,7 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; } case 'f': { - NSString *searchPath = [[NSString alloc] initWithUTF8String:optarg]; + searchPath = [[NSString alloc] initWithUTF8String:optarg]; searchPath = [searchPath stringByExpandingTildeInPath]; searchURL = [NSURL fileURLWithPath:searchPath]; break; @@ -74,6 +77,11 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; break; } + case 'u': { + uberMode = YES; + break; + } + default: break; } @@ -101,6 +109,8 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; CGUCodeGenTool *target = [self new]; target.inputURL = url; + target.uberMode = uberMode; + target.searchPath = searchPath; target.targetiOS6 = target6; target.classPrefix = classPrefix; target.toolName = [[NSString stringWithUTF8String:argv[0]] lastPathComponent]; @@ -129,14 +139,24 @@ - (void)writeOutputFiles; NSURL *interfaceURL = [currentDirectory URLByAppendingPathComponent:classNameH]; NSURL *implementationURL = [currentDirectory URLByAppendingPathComponent:classNameM]; - [self.interfaceContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - return [obj1 compare:obj2]; - }]; - [self.implementationContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - return [obj1 compare:obj2]; - }]; + if (!self.uberMode) { + // uber mode generates classes, so we cannot reorder the lines of generated code + [self.interfaceContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2]; + }]; + [self.implementationContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2]; + }]; + } NSMutableString *interface = [NSMutableString stringWithFormat:@"//\n// This file is generated from %@ by %@.\n// Please do not edit.\n//\n\n#import \n\n\n", self.inputURL.lastPathComponent, self.toolName]; + + // remove duplicates + NSArray *uniqueImports = [[NSSet setWithArray:self.interfaceImports] allObjects]; + for (NSString *import in uniqueImports) { + [interface appendFormat:@"#import %@\n", import]; + } + [interface appendString:@"\n"]; if (self.skipClassDeclaration) { [interface appendString:[self.interfaceContents componentsJoinedByString:@""]]; diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index c2af334..285e241 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -10,12 +10,40 @@ #import "IDStoryboardDumper.h" -@interface NSString (IDStoryboardAddition) +@implementation NSString (IDStoryboardAddition) - (NSString *)IDS_titlecaseString; +{ + NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSMutableString *output = [NSMutableString string]; + for (NSString *word in words) { + [output appendFormat:@"%@%@", [[word substringToIndex:1] uppercaseString], [word substringFromIndex:1]]; + } + return output; +} -@end +- (NSString *)IDS_camelcaseString; +{ + NSString *output = [self IDS_titlecaseString]; + output = [NSString stringWithFormat:@"%@%@", [[output substringToIndex:1] lowercaseString], [output substringFromIndex:1]]; + return output; +} +- (NSString *)IDS_stringWithSuffix:(NSString *)suffix { + if (![self hasSuffix:suffix]) { + return [self stringByAppendingString:suffix]; + } + return self; +} + +- (NSString *)IDS_asPrefixOf:(NSString *)suffix { + if (![suffix hasPrefix:self]) { + return [self stringByAppendingString:suffix]; + } + return self; +} + +@end @implementation IDStoryboardDumper @@ -24,6 +52,40 @@ + (NSString *)inputFileExtension; return @"storyboard"; } +- (NSString *)classTypeForViewControllerElement:(NSXMLElement *)viewControllerElement +{ + // element.name is the view controller type (e.g. tableViewController, navigationController, etc.) + return [[viewControllerElement attributeForName:@"customClass"] stringValue] ?: [@"UI" stringByAppendingString:[viewControllerElement.name IDS_titlecaseString]]; +} + +- (void)importClass:(NSString *)className +{ + NSTask *findFiles = [NSTask new]; + [findFiles setLaunchPath:@"/usr/bin/grep"]; + [findFiles setCurrentDirectoryPath:self.searchPath]; + [findFiles setArguments:[[NSString stringWithFormat:@"-r -l -e @interface[[:space:]]\\{1,\\}%@[[:space:]]*:[[:space:]]*[[:alpha:]]\\{1,\\} .", className] componentsSeparatedByString:@" "]]; + + NSPipe *pipe = [NSPipe pipe]; + [findFiles setStandardOutput:pipe]; + NSFileHandle *file = [pipe fileHandleForReading]; + + [findFiles launch]; + [findFiles waitUntilExit]; + + NSData *data = [file readDataToEndOfFile]; + + NSString *string = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]; + NSArray *lines = [string componentsSeparatedByString:@"\n"]; + for (NSString *line in lines) { + NSURL *path = [NSURL URLWithString:line]; + NSString *importFile = [path lastPathComponent]; + if ([importFile hasSuffix:@".h"]) { + [self.interfaceImports addObject:[NSString stringWithFormat:@"\"%@\"", importFile]]; + break; + } + } +} + - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; { self.skipClassDeclaration = YES; @@ -44,10 +106,172 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; self.interfaceContents = [NSMutableArray array]; self.implementationContents = [NSMutableArray array]; - + NSMutableDictionary *uniqueKeys = [NSMutableDictionary dictionary]; uniqueKeys[[NSString stringWithFormat:@"%@%@StoryboardName", self.classPrefix, storyboardName]] = storyboardFilename; + if (self.uberMode) { + self.interfaceImports = [NSMutableArray array]; + NSMutableArray *viewControllers = [NSMutableArray array];; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//viewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tableViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//collectionViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//pageViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//navigationController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tabBarController" error:&error]]; + // TODO: add support for GLKViewControllers + + [viewControllers sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSString *storyboardIdentifier1 = [[[obj1 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; + NSString *storyboardIdentifier2 = [[[obj2 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; + return [storyboardIdentifier1 caseInsensitiveCompare:storyboardIdentifier2]; + }]; + + NSString *nonUberStoryboardNameKey = [NSString stringWithFormat:@"%@%@StoryboardName", self.classPrefix, storyboardName]; + [uniqueKeys removeObjectForKey:nonUberStoryboardNameKey]; + NSString *storyboardClassName = [self.classPrefix IDS_asPrefixOf:[NSString stringWithFormat:@"%@Storyboard", storyboardName]]; + + // output @interface MYMainStoryboard : NSObject + [self.interfaceContents addObject:[NSString stringWithFormat:@"@interface %@ : NSObject\n", storyboardClassName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"@implementation %@\n", storyboardClassName]]; + + // output + [MYMainStoryboard storyboard] + [self.interfaceContents addObject:@"+ (UIStoryboard *)storyboard;\n"]; + [self.implementationContents addObject:@"+ (UIStoryboard *)storyboard {\n"]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return [UIStoryboard storyboardWithName:@\"%@\" bundle:nil];\n", storyboardName]]; + [self.implementationContents addObject:@"}\n"]; + + // output + [MYMainStoryboard instantiateInitialViewController] + NSString *initialViewControllerID = [[[document rootElement] attributeForName:@"initialViewController"] stringValue]; + if (initialViewControllerID) { + for (NSXMLElement *viewControllerElement in viewControllers) { + if (![[[viewControllerElement attributeForName:@"id"] stringValue] isEqualToString:initialViewControllerID]) + continue; + + // found initial view controller + NSString *customClass = [self classTypeForViewControllerElement:viewControllerElement]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (%@ *)instantiateInitialViewController;\n", customClass]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"+ (%@ *)instantiateInitialViewController {\n", customClass]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return [[self storyboard] instantiateInitialViewController];\n"]]; + [self.implementationContents addObject:@"}\n"]; + break; + } + } + + for (NSXMLElement *viewControllerElement in viewControllers) { + NSString *storyboardIdentifier = [[viewControllerElement attributeForName:@"storyboardIdentifier"] stringValue]; + NSString *customClass = [[viewControllerElement attributeForName:@"customClass"] stringValue]; + if (customClass) { + // output #import "MYCustomViewController.h" + [self importClass:customClass]; + } + + if (!storyboardIdentifier) { + continue; + } + + [identifiers removeObject:storyboardIdentifier]; // prevent user from using the old strings + NSString *className = [self classTypeForViewControllerElement:viewControllerElement]; + + + NSString *methodName = [@"instantiate" stringByAppendingString:[[storyboardIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Controller"]]; + + // output + [MYMainStoryboard instatiateMyCustomViewController] + [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (%@ *)%@;\n", className, methodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"+ (%@ *)%@ {\n", className, methodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return [[self storyboard] instantiateViewControllerWithIdentifier:@\"%@\"];\n", storyboardIdentifier]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + } + [self.interfaceContents addObject:@"@end\n\n"]; + [self.implementationContents addObject:@"@end\n\n"]; + + NSInteger uniqueNumber = 0; // TODO: instead of using this hack, combine all the methods into a single category. Also deal with multiple storyboards that reference the same class. + for (NSXMLElement *viewControllerElement in viewControllers) { + NSString *customClass = [[viewControllerElement attributeForName:@"customClass"] stringValue]; + if (!customClass) { + continue; + } + + NSArray *segueIdentifiers = [[viewControllerElement nodesForXPath:@".//segue/@identifier" error:&error] valueForKey:NSStringFromSelector(@selector(stringValue))]; + NSArray *reuseIdentifiers = [viewControllerElement nodesForXPath:@".//*[@reuseIdentifier]" error:&error]; + if (segueIdentifiers.count == 0 && reuseIdentifiers.count == 0) { + // nothing to output + continue; + } + + // output @interface MYCustomViewController (ObjcCodeGenUtils) + NSString *categoryName = [NSString stringWithFormat:@"ObjcCodeGenUtils_%@_%ld", storyboardName, (long)uniqueNumber++]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"@interface %@ (%@)\n", customClass, categoryName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"@implementation %@ (%@)\n", customClass, categoryName]]; + + for (NSString *segueIdentifier in segueIdentifiers) { + [identifiers removeObject:segueIdentifier]; // we don't want the user accessing this segue via the old method + + // output + [(MYCustomViewController *) myCustomSegueIdentifier] + NSString *segueIdentifierMethodName = [[[segueIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Segue"] stringByAppendingString:@"Identifier"]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (NSString *)%@;\n", segueIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"+ (NSString *)%@ {\n", segueIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", segueIdentifier]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + + // output - [(MYCustomViewController *) myCustomSegueIdentifier] + [self.interfaceContents addObject:[NSString stringWithFormat:@"- (NSString *)%@;\n", segueIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"- (NSString *)%@ {\n", segueIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", segueIdentifier]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + + // output - [(MYCustomViewController *) performMyCustomSegue] + NSString *performSegueMethodName = [[segueIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Segue"]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"- (void)perform%@;\n", performSegueMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"- (void)perform%@ {\n", performSegueMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" [self performSegueWithIdentifier:[self %@] sender:nil];\n", segueIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + } + + for (NSXMLElement *reuseIdentifierElement in reuseIdentifiers) { + NSString *customClass = [[reuseIdentifierElement attributeForName:@"customClass"] stringValue]; + if (customClass) { + [self importClass:customClass]; + } + NSString *elementName = reuseIdentifierElement.name; // E.g. collectionViewCell, tableViewCell, etc. + NSString *className = customClass ?: [@"UI" stringByAppendingString:[reuseIdentifierElement.name IDS_titlecaseString]]; + NSString *reuseIdentifier = [[reuseIdentifierElement attributeForName:@"reuseIdentifier"] stringValue]; + [identifiers removeObject:reuseIdentifier]; + + NSString *methodNameSecondArgument = nil; + NSString *code = nil; + + if ([elementName isEqualToString:@"tableViewCell"]) { + methodNameSecondArgument = @"ofTableView:(UITableView *)tableView"; + code = [NSString stringWithFormat:@"[tableView dequeueReusableCellWithIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; + } else if ([elementName isEqualToString:@"collectionViewCell"] || [elementName isEqualToString:@"collectionReusableView"]) { + methodNameSecondArgument = @"ofCollectionView:(UICollectionView *)collectionView"; + code = [NSString stringWithFormat:@"[collectionView dequeueReusableCellWithReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; + } + // TODO: add support for [collectionView dequeueReusableSupplementaryViewOfKind:(NSString *) withReuseIdentifier:(NSString *) forIndexPath:(NSIndexPath *)] + + // output - (NSString *)[(MYCustomViewController *) myCustomCellIdentifier]; + NSString *reuseIdentifierMethodName = [[reuseIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Identifier"]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"- (NSString *)%@;\n", reuseIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"- (NSString *)%@ {\n", reuseIdentifierMethodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", reuseIdentifier]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + + + // output - (MYCustomCell *)[(MYCustomViewController *) dequeueMyCustomCellForIndexPath:ofTableView:] + NSString *methodName = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Cell"], methodNameSecondArgument]; + [self.interfaceContents addObject:[NSString stringWithFormat:@"- (%@ *)%@;\n", className, methodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"- (%@ *)%@ {\n", className, methodName]]; + [self.implementationContents addObject:[NSString stringWithFormat:@" return %@;\n", code]]; + [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + } + + + [self.interfaceContents addObject:@"@end\n\n"]; + [self.implementationContents addObject:@"@end\n\n"]; + } + } + for (NSString *identifier in identifiers) { NSString *key = [NSString stringWithFormat:@"%@%@Storyboard%@Identifier", self.classPrefix, storyboardName, [identifier IDS_titlecaseString]]; uniqueKeys[key] = identifier; @@ -62,18 +286,3 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; } @end - - -@implementation NSString (IDStoryboardAddition) - -- (NSString *)IDS_titlecaseString; -{ - NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSMutableString *output = [NSMutableString string]; - for (NSString *word in words) { - [output appendFormat:@"%@%@", [[word substringToIndex:1] uppercaseString], [word substringFromIndex:1]]; - } - return output; -} - -@end From cd43136fe0dd68ac76f2e92d5d276dfcedbc702e Mon Sep 17 00:00:00 2001 From: OY Date: Wed, 2 Apr 2014 21:33:51 -0700 Subject: [PATCH 02/13] Removed uber mode setting. Removed uniqueNumber hack, and replaced with a more robust class generator. --- CodeGenTestApp/CGTAMasterViewController.m | 72 ++--- .../CodeGenTestApp.xcodeproj/project.pbxproj | 22 +- ...ode.storyboard => MoreExamples.storyboard} | 0 Shared/CGUCodeGenTool.h | 50 +++- Shared/CGUCodeGenTool.m | 139 +++++++-- identifierconstants/IDStoryboardDumper.m | 274 +++++++++--------- 6 files changed, 338 insertions(+), 219 deletions(-) rename CodeGenTestApp/{UberMode.storyboard => MoreExamples.storyboard} (100%) diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index e555f71..81081a0 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -12,12 +12,6 @@ #import "CGTAImagesCatalog+RuntimeHackery.h" #import "CGTAMainStoryboardIdentifiers.h" -// To disable uber mode: -// 1. Comment out the "#define UBER_MODE" below -// 2. Go to the target's build phases settings, and remove the -u option from the objc-identifierconstants command -#define UBER_MODE - - @interface CGTAMasterViewController () @property (nonatomic, weak) IBOutlet UISlider *cellSizeSlider; @@ -38,76 +32,41 @@ - (void)awakeFromNib; - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { -#ifndef UBER_MODE // New version: get the properly compiler-checked spelling from the storyboard. - if ([segue.identifier isEqualToString:CGTAMainStoryboardTapOnFlagIdentifier]) { - CGTADetailViewController *detailViewController = segue.destinationViewController; - detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; - } -#else - // But really, why not use class methods? - // ... here we are guaranteed that topOnFlag is our own view controller's segue and not some random one in the storyboard + // ... here we are guaranteed that tapOnFlag is one of our own view controller's segue and not some random one in the storyboard if ([segue.identifier isEqualToString:[self tapOnFlagSegueIdentifier]]) { CGTADetailViewController *detailViewController = segue.destinationViewController; detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; } -#endif } - (IBAction)pushTapped:(id)sender { CGTADetailViewController *detailViewController = nil; -#ifndef UBER_MODE - UIStoryboard *storyboard = nil; - // Initial version: full of strings that you have to type correctly! // Misspell any of these and your app will not work as expected. - storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; detailViewController = [storyboard instantiateViewControllerWithIdentifier:@"Detail View Controller"]; - // New version: get the properly compiler-checked spelling from the storyboard. - storyboard = [UIStoryboard storyboardWithName:CGTAMainStoryboardName bundle:nil]; - // ... there is no guarantee that the storyboard actually has an identifier named CGTAMainStoryboardDetailViewControllerIdentifier. We are using two different constants that we must manually guarantee are in sync. - detailViewController = [storyboard instantiateViewControllerWithIdentifier:CGTAMainStoryboardDetailViewControllerIdentifier]; - - detailViewController.image = [CGTAImagesCatalog usaImage]; - // ... also, we have no guarantee that this view controller is an instance of CGTADetailViewController, thus accessing the .image property may case another error. - [self.navigationController pushViewController:detailViewController animated:YES]; -#else - -#if 0 - // Here is example of a crash that might happen (especially if we reorganize our storyboards): - storyboard = [UIStoryboard storyboardWithName:CGTAMainStoryboardName bundle:nil]; - detailViewController = [storyboard instantiateViewControllerWithIdentifier:CGTAUberModeStoryboardDetailViewControllerIdentifier]; - // the above will crash because of the discrepency between cgtaMAINstoryboardname and cgtaUBERMODEstoryboarddetailveiwcontrolleridentifier - // the compiler will never catch these kind of mistakes. Unless we use Uber Mode... -#endif - - // Then really, why not use class methods? + // New version: the two lines are combined into one ensuring that "Detail View Controller" does indeed belong to the "Main" storyboard detailViewController = [CGTAMainStoryboard instantiateDetailViewController]; - // ... two lines became one, guaranteeing the previous error will never happen, - detailViewController.image = [CGTAImagesCatalog usaImage]; // ... also notice how this returns a CGTADetailViewController, rather than an id, so we can be assured that .image is a valid property! + detailViewController.image = [CGTAImagesCatalog usaImage]; [self.navigationController pushViewController:detailViewController animated:YES]; -#endif } - (IBAction)performTapped:(id)sender { -#ifndef UBER_MODE - // Initial version: full of strings that you have to type correctly! - // Misspell any of these and your app will not work as expected. - //[self performSegueWithIdentifier:@"Tap on Flag" sender:nil]; + // Initial version: uses a string that you have to type correctly! + // Misspell this and your app will not work as expected. +#if 0 + [self performSegueWithIdentifier:@"Tap on Flag" sender:nil]; +#endif // New version: get the properly compiler-checked spelling from the storyboard. - [self performSegueWithIdentifier:CGTAMainStoryboardTapOnFlagIdentifier sender:nil]; -#else - - // But really, why not use class methods? [self performTapOnFlagSegue]; -#endif } #pragma mark - Private methods @@ -148,11 +107,14 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; { -#ifndef UBER_MODE - CGTAFlagCollectionViewCell *cell = (CGTAFlagCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CGTAMainStoryboardImageCellIdentifier forIndexPath:indexPath]; -#else - CGTAFlagCollectionViewCell *cell = [self dequeueImageCellForIndexPath:indexPath ofCollectionView:collectionView]; -#endif + CGTAFlagCollectionViewCell *cell = nil; + + // Initial version: we must type in the identifier, and have no guarantees as to which class it returns + cell = (CGTAFlagCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"Image Cell" forIndexPath:indexPath]; + + // New version: class extension which returns the exact type we are expecting + cell = [self dequeueImageCellForIndexPath:indexPath ofCollectionView:collectionView]; + cell.imageView.image = self.flagImages[indexPath.item]; return cell; } diff --git a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj index 95e82eb..9f134bc 100644 --- a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj +++ b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj @@ -21,8 +21,8 @@ A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */ = {isa = PBXBuildFile; fileRef = A838793518A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.m */; }; A881854218A9B622002803FC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A83878E518A0367C00B386D6 /* main.m */; }; A881854418A9B663002803FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A83878EB18A0367C00B386D6 /* Main.storyboard */; }; - AA24EC5A18EB4F8E00DB0F94 /* UberMode.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */; }; - AA24EC5D18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */; }; + AA24EC5A18EB4F8E00DB0F94 /* MoreExamples.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */; }; + AAA9F41518ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -97,9 +97,9 @@ A881852518A9B512002803FC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; A881852718A9B520002803FC /* codegenutils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = codegenutils.xcodeproj; path = ../codegenutils.xcodeproj; sourceTree = ""; }; A89D8FE617CFFDCE0077F2B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UberMode.storyboard; sourceTree = ""; }; - AA24EC5B18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAUberModeStoryboardIdentifiers.h; sourceTree = ""; }; - AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAUberModeStoryboardIdentifiers.m; sourceTree = ""; }; + AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MoreExamples.storyboard; sourceTree = ""; }; + AAA9F41318ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAMoreExamplesStoryboardIdentifiers.h; sourceTree = ""; }; + AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAMoreExamplesStoryboardIdentifiers.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -122,7 +122,7 @@ A83878E818A0367C00B386D6 /* CGTAAppDelegate.h */, A83878E918A0367C00B386D6 /* CGTAAppDelegate.m */, A83878EB18A0367C00B386D6 /* Main.storyboard */, - AA24EC5918EB4F8E00DB0F94 /* UberMode.storyboard */, + AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */, A83878F418A0367C00B386D6 /* Images.xcassets */, A83878EE18A0367C00B386D6 /* CGTAMasterViewController.h */, A83878EF18A0367C00B386D6 /* CGTAMasterViewController.m */, @@ -152,8 +152,8 @@ A838791518A0455E00B386D6 /* CGTAImagesCatalog.m */, A838793118A0557E00B386D6 /* CGTAMainStoryboardIdentifiers.h */, A838793218A0557E00B386D6 /* CGTAMainStoryboardIdentifiers.m */, - AA24EC5B18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.h */, - AA24EC5C18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m */, + AAA9F41318ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.h */, + AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */, A838791818A04AB300B386D6 /* CGTATestAppColorList.h */, A838791918A04AB300B386D6 /* CGTATestAppColorList.m */, A838793418A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.h */, @@ -288,7 +288,7 @@ buildActionMask = 2147483647; files = ( A881854418A9B663002803FC /* Main.storyboard in Resources */, - AA24EC5A18EB4F8E00DB0F94 /* UberMode.storyboard in Resources */, + AA24EC5A18EB4F8E00DB0F94 /* MoreExamples.storyboard in Resources */, A881853A18A9B614002803FC /* Images.xcassets in Resources */, A83878E418A0367C00B386D6 /* InfoPlist.strings in Resources */, ); @@ -309,7 +309,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "DERIVED_SOURCES=\"$SRCROOT/Derived Sources\"\nTOOL_PATH=$BUILD_ROOT/$CONFIGURATION\n$TOOL_PATH/objc-assetgen -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-colordump -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-identifierconstants -u -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n"; + shellScript = "DERIVED_SOURCES=\"$SRCROOT/Derived Sources\"\nTOOL_PATH=$BUILD_ROOT/$CONFIGURATION\n$TOOL_PATH/objc-assetgen -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-colordump -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n$TOOL_PATH/objc-identifierconstants -f \"$SRCROOT\" -o \"$DERIVED_SOURCES\" -p CGTA\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -318,11 +318,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AAA9F41518ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m in Sources */, A881853918A9B609002803FC /* CGTAAppDelegate.m in Sources */, A881853C18A9B622002803FC /* CGTAMasterViewController.m in Sources */, A881853F18A9B622002803FC /* CGTAMainStoryboardIdentifiers.m in Sources */, A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */, - AA24EC5D18EB537300DB0F94 /* CGTAUberModeStoryboardIdentifiers.m in Sources */, A881853E18A9B622002803FC /* CGTAImagesCatalog.m in Sources */, A881854018A9B622002803FC /* CGTATestAppColorList.m in Sources */, A881854218A9B622002803FC /* main.m in Sources */, diff --git a/CodeGenTestApp/UberMode.storyboard b/CodeGenTestApp/MoreExamples.storyboard similarity index 100% rename from CodeGenTestApp/UberMode.storyboard rename to CodeGenTestApp/MoreExamples.storyboard diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index 6913c43..a5762e1 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -9,6 +9,11 @@ #import +typedef NS_ENUM(NSInteger, CGUClassType) { + CGUClassType_Definition, + CGUClassType_Extension, + CGUClassType_Category +}; @interface CGUCodeGenTool : NSObject @@ -21,12 +26,13 @@ @property (copy) NSString *searchPath; @property BOOL targetiOS6; @property BOOL skipClassDeclaration; -@property BOOL uberMode; @property (copy) NSString *className; -@property (strong) NSMutableArray *interfaceContents; /// An array of strings such as "" which will be imported at the top of the .h file. @property (strong) NSMutableArray *interfaceImports; +/// A dictionary of class names as keys (NSString *), and CGUClass instances as values. +@property (strong) NSMutableDictionary *classes; +@property (strong) NSMutableArray *interfaceContents; @property (strong) NSMutableArray *implementationContents; - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; @@ -36,3 +42,43 @@ - (NSString *)methodNameForKey:(NSString *)key; @end + + + +@interface CGUClass : NSObject + +/// The class type is determined by the following: +/// - If there is a superClassName, this is a class definition +/// - If there is a clategoryName, this is a category +/// - Otherwise, this is a class extension +@property (readonly) CGUClassType classType; +@property (copy) NSString *categoryName; +/// An array of CGUMethods +@property (strong) NSMutableArray *methods; +@property (copy) NSString *name; +@property (copy) NSString *superClassName; + +- (NSString *)interfaceCode; +- (NSString *)implementationCode; + +@end + + + +@interface CGUMethod : NSObject + +/// Specifies if this is an class method rather than an instance method. +@property BOOL classMethod; + +/// E.g. "NSString *" +/// If this is nil, it will be replaced with void. +@property NSString *returnType; + +/// E.g. "doSomethingWithString:(NSString *)myString andNumber:(NSInteger)number" +@property (copy) NSString *nameAndArguments; +@property (copy) NSString *body; + +- (NSString *)interfaceCode; +- (NSString *)implementationCode; + +@end diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 4959ef1..fc10f0c 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -34,10 +34,9 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; NSString *searchPath = nil; NSString *classPrefix = @""; BOOL target6 = NO; - BOOL uberMode = NO; NSMutableArray *inputURLs = [NSMutableArray array]; - while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6u")) != -1) { + while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6")) != -1) { switch (opt) { case 'h': { printf("Usage: %s [-6] [-u] [-o ] [-f ] [-p ] []\n", basename((char *)argv[0])); @@ -47,7 +46,6 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; printf(" -o Output files at \n"); printf(" -f Search for *.%s folders starting from \n", [[self inputFileExtension] UTF8String]); printf(" -p Use as the class prefix in the generated code\n"); - printf(" -u Uber mode\n"); printf(" -h Print this help and exit\n"); printf(" Input files; this and/or -f are required.\n"); return 0; @@ -77,11 +75,6 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; break; } - case 'u': { - uberMode = YES; - break; - } - default: break; } @@ -109,7 +102,6 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; CGUCodeGenTool *target = [self new]; target.inputURL = url; - target.uberMode = uberMode; target.searchPath = searchPath; target.targetiOS6 = target6; target.classPrefix = classPrefix; @@ -139,24 +131,26 @@ - (void)writeOutputFiles; NSURL *interfaceURL = [currentDirectory URLByAppendingPathComponent:classNameH]; NSURL *implementationURL = [currentDirectory URLByAppendingPathComponent:classNameM]; - if (!self.uberMode) { - // uber mode generates classes, so we cannot reorder the lines of generated code - [self.interfaceContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - return [obj1 compare:obj2]; - }]; - [self.implementationContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - return [obj1 compare:obj2]; - }]; - } + // uber mode generates classes, so we cannot reorder the lines of generated code + [self.interfaceContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2]; + }]; + [self.implementationContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + return [obj1 compare:obj2]; + }]; NSMutableString *interface = [NSMutableString stringWithFormat:@"//\n// This file is generated from %@ by %@.\n// Please do not edit.\n//\n\n#import \n\n\n", self.inputURL.lastPathComponent, self.toolName]; - // remove duplicates - NSArray *uniqueImports = [[NSSet setWithArray:self.interfaceImports] allObjects]; + NSArray *uniqueImports = [[NSSet setWithArray:self.interfaceImports] allObjects]; // removes duplicate imports for (NSString *import in uniqueImports) { [interface appendFormat:@"#import %@\n", import]; } [interface appendString:@"\n"]; + + for (NSString *className in self.classes) { + CGUClass *class = self.classes[className]; + [interface appendFormat:@"%@\n", [class interfaceCode]]; + } if (self.skipClassDeclaration) { [interface appendString:[self.interfaceContents componentsJoinedByString:@""]]; @@ -169,6 +163,12 @@ - (void)writeOutputFiles; } NSMutableString *implementation = [NSMutableString stringWithFormat:@"//\n// This file is generated from %@ by %@.\n// Please do not edit.\n//\n\n#import \"%@\"\n\n\n", self.inputURL.lastPathComponent, self.toolName, classNameH]; + + for (NSString *className in self.classes) { + CGUClass *class = self.classes[className]; + [implementation appendFormat:@"%@\n", [class implementationCode]]; + } + if (self.skipClassDeclaration) { [implementation appendString:[self.implementationContents componentsJoinedByString:@""]]; } else { @@ -198,3 +198,102 @@ - (NSString *)methodNameForKey:(NSString *)key; } @end + + + +@implementation CGUClass + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.methods = [NSMutableArray array]; + } + return self; +} + +- (void)sortMethods { + [self.methods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + CGUMethod *method1 = obj1; + CGUMethod *method2 = obj2; + + // 1. sort class methods first, then instance methods + if (method1.classMethod && !method2.classMethod) { + return NSOrderedAscending; + } else if (!method1.classMethod && method2.classMethod) { + return NSOrderedDescending; + } + + // 2. sort by the method name + return [method1.nameAndArguments caseInsensitiveCompare:method2.nameAndArguments]; + }]; +} + +- (NSString *)interfaceCode { + if (self.methods.count == 0 && self.classType != CGUClassType_Definition) { + // no need to print a category/extension if it has no methods + return @""; + } + + [self sortMethods]; + + NSMutableString *result = [NSMutableString string]; + if (self.classType == CGUClassType_Definition) { + [result appendFormat:@"@interface %@ : %@\n", self.name, self.superClassName]; + } else { + [result appendFormat:@"@interface %@ (%@)\n", self.name, self.categoryName]; + } + for (CGUMethod *method in self.methods) { + [result appendString:[method interfaceCode]]; + [result appendString:@"\n"]; + } + [result appendFormat:@"@end\n"]; + return result; +} + +- (NSString *)implementationCode { + if (self.methods.count == 0 && self.classType != CGUClassType_Definition) { + // no need to print a category/extension if it has no methods + return @""; + } + + [self sortMethods]; + + NSMutableString *result = [NSMutableString string]; + if (self.classType == CGUClassType_Definition) { + [result appendFormat:@"@implementation %@\n", self.name]; + } else { + [result appendFormat:@"@implementation %@ (%@)\n", self.name, self.categoryName]; + } + for (CGUMethod *method in self.methods) { + [result appendString:[method implementationCode]]; + [result appendString:@"\n"]; + } + [result appendFormat:@"@end\n"]; + return result; +} + +- (CGUClassType)classType { + if (self.superClassName) { + return CGUClassType_Definition; + } else { + return self.categoryName.length == 0 ? CGUClassType_Extension : CGUClassType_Category; + } +} + +@end + + + +@implementation CGUMethod + +- (NSString *)interfaceCode { + return [NSString stringWithFormat:@"%@ (%@)%@;", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments]; +} + +- (NSString *)implementationCode { + // TODO: indent each line in the body? + return [NSString stringWithFormat:@"%@ (%@)%@ {\n%@\n}", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments, self.body]; +} + +@end \ No newline at end of file diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 285e241..d03caa2 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -9,6 +9,9 @@ #import "IDStoryboardDumper.h" +@interface IDStoryboardDumper () +@property (strong) NSMutableDictionary *classesImported; +@end @implementation NSString (IDStoryboardAddition) @@ -52,14 +55,40 @@ + (NSString *)inputFileExtension; return @"storyboard"; } -- (NSString *)classTypeForViewControllerElement:(NSXMLElement *)viewControllerElement +/// element is any that have a customClass attribute and contain the valid default class name as their name (e.g. viewController, or tableViewCell) +- (NSString *)classTypeForElement:(NSXMLElement *)element importedCustomClass:(BOOL *)importedCustomClass { - // element.name is the view controller type (e.g. tableViewController, navigationController, etc.) - return [[viewControllerElement attributeForName:@"customClass"] stringValue] ?: [@"UI" stringByAppendingString:[viewControllerElement.name IDS_titlecaseString]]; + if (importedCustomClass) { + *importedCustomClass = NO; + } + + NSString *customClass = [[element attributeForName:@"customClass"] stringValue]; + if (customClass && [self importClass:customClass]) { + // we can use the custom class + if (importedCustomClass) { + *importedCustomClass = YES; + } + return customClass; + } else { + // element.name is the view controller type (e.g. tableViewController, navigationController, etc.) + NSString *defaultClass = [@"UI" stringByAppendingString:[element.name IDS_titlecaseString]]; + return defaultClass; + } } -- (void)importClass:(NSString *)className +/// You may call this method multiple times with the same className without it having to search the search path each time. It will only search once and cache the result. +- (BOOL)importClass:(NSString *)className { + /// Keys: NSString of class name; Values: @(BOOL) stating if it was successfully imported or not + if (self.classesImported == nil) { + self.classesImported = [NSMutableDictionary dictionary]; + } + + if (self.classesImported[className]) { + // if we have arleady tried searching for this class, there is no need to search for it again + return [self.classesImported[className] boolValue]; + } + NSTask *findFiles = [NSTask new]; [findFiles setLaunchPath:@"/usr/bin/grep"]; [findFiles setCurrentDirectoryPath:self.searchPath]; @@ -76,14 +105,22 @@ - (void)importClass:(NSString *)className NSString *string = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]; NSArray *lines = [string componentsSeparatedByString:@"\n"]; + BOOL successfullyImported = NO; for (NSString *line in lines) { NSURL *path = [NSURL URLWithString:line]; NSString *importFile = [path lastPathComponent]; if ([importFile hasSuffix:@".h"]) { [self.interfaceImports addObject:[NSString stringWithFormat:@"\"%@\"", importFile]]; + successfullyImported = YES; break; } } + + if (!successfullyImported) { + NSLog(@"Unable to find class interface for '%@'. Reverting to global string constant behavior.", className); + } + self.classesImported[className] = @(successfullyImported); + return successfullyImported; } - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; @@ -104,174 +141,149 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; [identifiers addObjectsFromArray:reuseIdentifiers]; [identifiers addObjectsFromArray:segueIdentifiers]; + self.interfaceImports = [NSMutableArray array]; + self.classes = [NSMutableDictionary dictionary]; self.interfaceContents = [NSMutableArray array]; self.implementationContents = [NSMutableArray array]; - - NSMutableDictionary *uniqueKeys = [NSMutableDictionary dictionary]; - uniqueKeys[[NSString stringWithFormat:@"%@%@StoryboardName", self.classPrefix, storyboardName]] = storyboardFilename; - if (self.uberMode) { - self.interfaceImports = [NSMutableArray array]; - NSMutableArray *viewControllers = [NSMutableArray array];; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//viewController" error:&error]]; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tableViewController" error:&error]]; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//collectionViewController" error:&error]]; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//pageViewController" error:&error]]; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//navigationController" error:&error]]; - [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tabBarController" error:&error]]; - // TODO: add support for GLKViewControllers - - [viewControllers sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { - NSString *storyboardIdentifier1 = [[[obj1 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; - NSString *storyboardIdentifier2 = [[[obj2 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; - return [storyboardIdentifier1 caseInsensitiveCompare:storyboardIdentifier2]; - }]; - - NSString *nonUberStoryboardNameKey = [NSString stringWithFormat:@"%@%@StoryboardName", self.classPrefix, storyboardName]; - [uniqueKeys removeObjectForKey:nonUberStoryboardNameKey]; - NSString *storyboardClassName = [self.classPrefix IDS_asPrefixOf:[NSString stringWithFormat:@"%@Storyboard", storyboardName]]; - - // output @interface MYMainStoryboard : NSObject - [self.interfaceContents addObject:[NSString stringWithFormat:@"@interface %@ : NSObject\n", storyboardClassName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"@implementation %@\n", storyboardClassName]]; - - // output + [MYMainStoryboard storyboard] - [self.interfaceContents addObject:@"+ (UIStoryboard *)storyboard;\n"]; - [self.implementationContents addObject:@"+ (UIStoryboard *)storyboard {\n"]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return [UIStoryboard storyboardWithName:@\"%@\" bundle:nil];\n", storyboardName]]; - [self.implementationContents addObject:@"}\n"]; - - // output + [MYMainStoryboard instantiateInitialViewController] - NSString *initialViewControllerID = [[[document rootElement] attributeForName:@"initialViewController"] stringValue]; - if (initialViewControllerID) { - for (NSXMLElement *viewControllerElement in viewControllers) { - if (![[[viewControllerElement attributeForName:@"id"] stringValue] isEqualToString:initialViewControllerID]) - continue; - + + NSMutableArray *viewControllers = [NSMutableArray array];; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//viewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tableViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//collectionViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//pageViewController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//navigationController" error:&error]]; + [viewControllers addObjectsFromArray:[document nodesForXPath:@"//tabBarController" error:&error]]; + // TODO: add support for GLKViewControllers + + [viewControllers sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSString *storyboardIdentifier1 = [[[obj1 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; + NSString *storyboardIdentifier2 = [[[obj2 attributeForName:@"storyboardIdentifier"] stringValue] IDS_titlecaseString]; + return [storyboardIdentifier1 caseInsensitiveCompare:storyboardIdentifier2]; + }]; + + CGUClass *storyboardClass = [CGUClass new]; + storyboardClass.name = [self.classPrefix IDS_asPrefixOf:[NSString stringWithFormat:@"%@Storyboard", storyboardName]]; + storyboardClass.superClassName = @"NSObject"; + self.classes[storyboardClass.name] = storyboardClass; + + // output + [MYMainStoryboard storyboard] + CGUMethod *storyboardMethod = [CGUMethod new]; + storyboardMethod.classMethod = YES; + storyboardMethod.returnType = @"UIStoryboard *"; + storyboardMethod.nameAndArguments = @"storyboard"; + storyboardMethod.body = [NSString stringWithFormat:@" return [UIStoryboard storyboardWithName:@\"%@\" bundle:nil];", storyboardName]; + [storyboardClass.methods addObject:storyboardMethod]; + + NSString *initialViewControllerID = [[[document rootElement] attributeForName:@"initialViewController"] stringValue]; + if (initialViewControllerID) { + NSString *initialViewControllerClass = nil; + for (NSXMLElement *viewControllerElement in viewControllers) { + if ([[[viewControllerElement attributeForName:@"id"] stringValue] isEqualToString:initialViewControllerID]) { // found initial view controller - NSString *customClass = [self classTypeForViewControllerElement:viewControllerElement]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (%@ *)instantiateInitialViewController;\n", customClass]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"+ (%@ *)instantiateInitialViewController {\n", customClass]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return [[self storyboard] instantiateInitialViewController];\n"]]; - [self.implementationContents addObject:@"}\n"]; + initialViewControllerClass = [self classTypeForElement:viewControllerElement importedCustomClass:NULL]; break; } } - for (NSXMLElement *viewControllerElement in viewControllers) { - NSString *storyboardIdentifier = [[viewControllerElement attributeForName:@"storyboardIdentifier"] stringValue]; - NSString *customClass = [[viewControllerElement attributeForName:@"customClass"] stringValue]; - if (customClass) { - // output #import "MYCustomViewController.h" - [self importClass:customClass]; - } - - if (!storyboardIdentifier) { - continue; - } - - [identifiers removeObject:storyboardIdentifier]; // prevent user from using the old strings - NSString *className = [self classTypeForViewControllerElement:viewControllerElement]; + if (initialViewControllerClass) { + // output + [MYMainStoryboard instantiateInitialViewController] + CGUMethod *instantiateInitialViewControllerMethod = [CGUMethod new]; + instantiateInitialViewControllerMethod.classMethod = YES; + instantiateInitialViewControllerMethod.returnType = [NSString stringWithFormat:@"%@ *", initialViewControllerClass]; + instantiateInitialViewControllerMethod.nameAndArguments = @"instantiateInitialViewController"; + instantiateInitialViewControllerMethod.body = @" return [[self storyboard] instantiateInitialViewController];"; + [storyboardClass.methods addObject:instantiateInitialViewControllerMethod]; + } else { + NSLog(@"Warning: Initial view controller exists, but wasn't found in the storyboard: %@", initialViewControllerID); + } + } + + for (NSXMLElement *viewControllerElement in viewControllers) { + NSString *storyboardIdentifier = [[viewControllerElement attributeForName:@"storyboardIdentifier"] stringValue]; + BOOL importedCustomClass = NO; + NSString *className = [self classTypeForElement:viewControllerElement importedCustomClass:&importedCustomClass]; + if (storyboardIdentifier) { + [identifiers removeObject:storyboardIdentifier]; // prevent user from using the old string, they can now access it via [MYMainStoryboard instantiate...] - - NSString *methodName = [@"instantiate" stringByAppendingString:[[storyboardIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Controller"]]; - - // output + [MYMainStoryboard instatiateMyCustomViewController] - [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (%@ *)%@;\n", className, methodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"+ (%@ *)%@ {\n", className, methodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return [[self storyboard] instantiateViewControllerWithIdentifier:@\"%@\"];\n", storyboardIdentifier]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + // output + [MYMainStoryboard instantiateMyCustomViewController] + CGUMethod *instantiateCustomViewControllerMethod = [CGUMethod new]; + instantiateCustomViewControllerMethod.classMethod = YES; + instantiateCustomViewControllerMethod.returnType = [NSString stringWithFormat:@"%@ *", className]; + instantiateCustomViewControllerMethod.nameAndArguments = [@"instantiate" stringByAppendingString:[[storyboardIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Controller"]]; + instantiateCustomViewControllerMethod.body = [NSString stringWithFormat:@" return [[self storyboard] instantiateViewControllerWithIdentifier:@\"%@\"];", storyboardIdentifier]; + [storyboardClass.methods addObject:instantiateCustomViewControllerMethod]; } - [self.interfaceContents addObject:@"@end\n\n"]; - [self.implementationContents addObject:@"@end\n\n"]; - NSInteger uniqueNumber = 0; // TODO: instead of using this hack, combine all the methods into a single category. Also deal with multiple storyboards that reference the same class. - for (NSXMLElement *viewControllerElement in viewControllers) { - NSString *customClass = [[viewControllerElement attributeForName:@"customClass"] stringValue]; - if (!customClass) { - continue; - } - + if (importedCustomClass) { NSArray *segueIdentifiers = [[viewControllerElement nodesForXPath:@".//segue/@identifier" error:&error] valueForKey:NSStringFromSelector(@selector(stringValue))]; NSArray *reuseIdentifiers = [viewControllerElement nodesForXPath:@".//*[@reuseIdentifier]" error:&error]; - if (segueIdentifiers.count == 0 && reuseIdentifiers.count == 0) { - // nothing to output - continue; - } - // output @interface MYCustomViewController (ObjcCodeGenUtils) - NSString *categoryName = [NSString stringWithFormat:@"ObjcCodeGenUtils_%@_%ld", storyboardName, (long)uniqueNumber++]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"@interface %@ (%@)\n", customClass, categoryName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"@implementation %@ (%@)\n", customClass, categoryName]]; + CGUClass *viewControllerClassCategory = self.classes[className]; // we may see the same class twice, so it is storyed in a dictionary + if (viewControllerClassCategory == nil) { + viewControllerClassCategory = [CGUClass new]; + viewControllerClassCategory.name = className; + viewControllerClassCategory.categoryName = [NSString stringWithFormat:@"ObjcCodeGenUtils_%@", storyboardName]; + self.classes[className] = viewControllerClassCategory; + } for (NSString *segueIdentifier in segueIdentifiers) { [identifiers removeObject:segueIdentifier]; // we don't want the user accessing this segue via the old method - // output + [(MYCustomViewController *) myCustomSegueIdentifier] - NSString *segueIdentifierMethodName = [[[segueIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Segue"] stringByAppendingString:@"Identifier"]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"+ (NSString *)%@;\n", segueIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"+ (NSString *)%@ {\n", segueIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", segueIdentifier]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; - // output - [(MYCustomViewController *) myCustomSegueIdentifier] - [self.interfaceContents addObject:[NSString stringWithFormat:@"- (NSString *)%@;\n", segueIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"- (NSString *)%@ {\n", segueIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", segueIdentifier]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + CGUMethod *segueIdentifierMethod = [CGUMethod new]; + segueIdentifierMethod.returnType = @"NSString *"; + segueIdentifierMethod.nameAndArguments = [[[segueIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Segue"] stringByAppendingString:@"Identifier"]; + segueIdentifierMethod.body = [NSString stringWithFormat:@" return @\"%@\";", segueIdentifier]; + [viewControllerClassCategory.methods addObject:segueIdentifierMethod]; // output - [(MYCustomViewController *) performMyCustomSegue] - NSString *performSegueMethodName = [[segueIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Segue"]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"- (void)perform%@;\n", performSegueMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"- (void)perform%@ {\n", performSegueMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" [self performSegueWithIdentifier:[self %@] sender:nil];\n", segueIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + CGUMethod *performSegueMethod = [CGUMethod new]; + performSegueMethod.nameAndArguments = [@"perform" stringByAppendingString:[[segueIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Segue"]]; + performSegueMethod.body = [NSString stringWithFormat:@" [self performSegueWithIdentifier:[self %@] sender:nil];", segueIdentifierMethod.nameAndArguments]; + [viewControllerClassCategory.methods addObject:performSegueMethod]; } for (NSXMLElement *reuseIdentifierElement in reuseIdentifiers) { - NSString *customClass = [[reuseIdentifierElement attributeForName:@"customClass"] stringValue]; - if (customClass) { - [self importClass:customClass]; - } - NSString *elementName = reuseIdentifierElement.name; // E.g. collectionViewCell, tableViewCell, etc. - NSString *className = customClass ?: [@"UI" stringByAppendingString:[reuseIdentifierElement.name IDS_titlecaseString]]; NSString *reuseIdentifier = [[reuseIdentifierElement attributeForName:@"reuseIdentifier"] stringValue]; [identifiers removeObject:reuseIdentifier]; + // output - (NSString *)[(MYCustomViewController *) myCustomCellIdentifier]; + CGUMethod *reuseIdentifierMethod = [CGUMethod new]; + reuseIdentifierMethod.returnType = @"NSString *"; + reuseIdentifierMethod.nameAndArguments = [[reuseIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Identifier"]; + reuseIdentifierMethod.body = [NSString stringWithFormat:@" return @\"%@\";", reuseIdentifier]; + [viewControllerClassCategory.methods addObject:reuseIdentifierMethod]; + + NSString *elementName = reuseIdentifierElement.name; // E.g. collectionViewCell, tableViewCell, etc. NSString *methodNameSecondArgument = nil; NSString *code = nil; - if ([elementName isEqualToString:@"tableViewCell"]) { methodNameSecondArgument = @"ofTableView:(UITableView *)tableView"; code = [NSString stringWithFormat:@"[tableView dequeueReusableCellWithIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; } else if ([elementName isEqualToString:@"collectionViewCell"] || [elementName isEqualToString:@"collectionReusableView"]) { methodNameSecondArgument = @"ofCollectionView:(UICollectionView *)collectionView"; code = [NSString stringWithFormat:@"[collectionView dequeueReusableCellWithReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; + } else { + NSLog(@"Warning: Unknown reuse identifier %@.", elementName); + continue; } - // TODO: add support for [collectionView dequeueReusableSupplementaryViewOfKind:(NSString *) withReuseIdentifier:(NSString *) forIndexPath:(NSIndexPath *)] - - // output - (NSString *)[(MYCustomViewController *) myCustomCellIdentifier]; - NSString *reuseIdentifierMethodName = [[reuseIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Identifier"]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"- (NSString *)%@;\n", reuseIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"- (NSString *)%@ {\n", reuseIdentifierMethodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return @\"%@\";\n", reuseIdentifier]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; - - + + NSString *reuseIdentifierClassName = [self classTypeForElement:reuseIdentifierElement importedCustomClass:NULL]; + // output - (MYCustomCell *)[(MYCustomViewController *) dequeueMyCustomCellForIndexPath:ofTableView:] - NSString *methodName = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Cell"], methodNameSecondArgument]; - [self.interfaceContents addObject:[NSString stringWithFormat:@"- (%@ *)%@;\n", className, methodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"- (%@ *)%@ {\n", className, methodName]]; - [self.implementationContents addObject:[NSString stringWithFormat:@" return %@;\n", code]]; - [self.implementationContents addObject:[NSString stringWithFormat:@"}\n"]]; + CGUMethod *dequeueMethod = [CGUMethod new]; + dequeueMethod.returnType = [NSString stringWithFormat:@"%@ *", reuseIdentifierClassName]; + dequeueMethod.nameAndArguments = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Cell"], methodNameSecondArgument]; + dequeueMethod.body = [NSString stringWithFormat:@" return %@;", code]; + [viewControllerClassCategory.methods addObject:dequeueMethod]; + + // TODO: add support for [collectionView dequeueReusableSupplementaryViewOfKind:(NSString *) withReuseIdentifier:(NSString *) forIndexPath:(NSIndexPath *)] } - - - [self.interfaceContents addObject:@"@end\n\n"]; - [self.implementationContents addObject:@"@end\n\n"]; } } + NSMutableDictionary *uniqueKeys = [NSMutableDictionary dictionary]; for (NSString *identifier in identifiers) { NSString *key = [NSString stringWithFormat:@"%@%@Storyboard%@Identifier", self.classPrefix, storyboardName, [identifier IDS_titlecaseString]]; uniqueKeys[key] = identifier; From 795d56025fd2a3ea6c4fbbaed8ca6c46a584597b Mon Sep 17 00:00:00 2001 From: OY Date: Thu, 3 Apr 2014 14:13:54 -0700 Subject: [PATCH 03/13] =?UTF-8?q?Various=20fixes=20according=20to=20puls?= =?UTF-8?q?=E2=80=99s=20suggestions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CodeGenTestApp/Base.lproj/Main.storyboard | 12 +------ CodeGenTestApp/CGTAMasterViewController.m | 32 +----------------- Shared/CGUCodeGenTool.h | 14 ++------ Shared/CGUCodeGenTool.m | 41 +++++++++++++++++------ identifierconstants/IDStoryboardDumper.m | 15 +++++---- 5 files changed, 43 insertions(+), 71 deletions(-) diff --git a/CodeGenTestApp/Base.lproj/Main.storyboard b/CodeGenTestApp/Base.lproj/Main.storyboard index b8de50c..32a3c38 100644 --- a/CodeGenTestApp/Base.lproj/Main.storyboard +++ b/CodeGenTestApp/Base.lproj/Main.storyboard @@ -51,24 +51,14 @@ - - - - - - + - - - - - diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index 81081a0..7a5e6e7 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -19,6 +19,7 @@ @interface CGTAMasterViewController () @end + @implementation CGTAMasterViewController #pragma mark - NSObject @@ -32,43 +33,12 @@ - (void)awakeFromNib; - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { - // New version: get the properly compiler-checked spelling from the storyboard. - // ... here we are guaranteed that tapOnFlag is one of our own view controller's segue and not some random one in the storyboard if ([segue.identifier isEqualToString:[self tapOnFlagSegueIdentifier]]) { CGTADetailViewController *detailViewController = segue.destinationViewController; detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; } } -- (IBAction)pushTapped:(id)sender -{ - CGTADetailViewController *detailViewController = nil; - - // Initial version: full of strings that you have to type correctly! - // Misspell any of these and your app will not work as expected. - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; - detailViewController = [storyboard instantiateViewControllerWithIdentifier:@"Detail View Controller"]; - - // New version: the two lines are combined into one ensuring that "Detail View Controller" does indeed belong to the "Main" storyboard - detailViewController = [CGTAMainStoryboard instantiateDetailViewController]; - - // ... also notice how this returns a CGTADetailViewController, rather than an id, so we can be assured that .image is a valid property! - detailViewController.image = [CGTAImagesCatalog usaImage]; - [self.navigationController pushViewController:detailViewController animated:YES]; -} - -- (IBAction)performTapped:(id)sender -{ - // Initial version: uses a string that you have to type correctly! - // Misspell this and your app will not work as expected. -#if 0 - [self performSegueWithIdentifier:@"Tap on Flag" sender:nil]; -#endif - - // New version: get the properly compiler-checked spelling from the storyboard. - [self performTapOnFlagSegue]; -} - #pragma mark - Private methods - (IBAction)sliderValueChanged:(UISlider *)sender; diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index a5762e1..265be44 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -9,11 +9,6 @@ #import -typedef NS_ENUM(NSInteger, CGUClassType) { - CGUClassType_Definition, - CGUClassType_Extension, - CGUClassType_Category -}; @interface CGUCodeGenTool : NSObject @@ -29,7 +24,7 @@ typedef NS_ENUM(NSInteger, CGUClassType) { @property (copy) NSString *className; /// An array of strings such as "" which will be imported at the top of the .h file. -@property (strong) NSMutableArray *interfaceImports; +@property (strong) NSMutableSet *interfaceImports; /// A dictionary of class names as keys (NSString *), and CGUClass instances as values. @property (strong) NSMutableDictionary *classes; @property (strong) NSMutableArray *interfaceContents; @@ -47,11 +42,6 @@ typedef NS_ENUM(NSInteger, CGUClassType) { @interface CGUClass : NSObject -/// The class type is determined by the following: -/// - If there is a superClassName, this is a class definition -/// - If there is a clategoryName, this is a category -/// - Otherwise, this is a class extension -@property (readonly) CGUClassType classType; @property (copy) NSString *categoryName; /// An array of CGUMethods @property (strong) NSMutableArray *methods; @@ -67,7 +57,7 @@ typedef NS_ENUM(NSInteger, CGUClassType) { @interface CGUMethod : NSObject -/// Specifies if this is an class method rather than an instance method. +/// Specifies if this is a class method rather than an instance method. @property BOOL classMethod; /// E.g. "NSString *" diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index fc10f0c..c29968c 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -11,6 +11,11 @@ #import +typedef NS_ENUM(NSInteger, CGUClassType) { + CGUClassType_Definition, + CGUClassType_Extension, + CGUClassType_Category +}; @interface CGUCodeGenTool () @@ -18,6 +23,16 @@ @interface CGUCodeGenTool () @end +@interface CGUClass () + +/// The class type is determined by the following: +/// - If there is a superClassName, this is a class definition +/// - If there is a clategoryName, this is a category +/// - Otherwise, this is a class extension +@property (readonly) CGUClassType classType; + +@end + @implementation CGUCodeGenTool @@ -39,7 +54,7 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6")) != -1) { switch (opt) { case 'h': { - printf("Usage: %s [-6] [-u] [-o ] [-f ] [-p ] []\n", basename((char *)argv[0])); + printf("Usage: %s [-6] [-o ] [-f ] [-p ] []\n", basename((char *)argv[0])); printf(" %s -h\n\n", basename((char *)argv[0])); printf("Options:\n"); printf(" -6 Target iOS 6 in addition to iOS 7\n"); @@ -131,7 +146,6 @@ - (void)writeOutputFiles; NSURL *interfaceURL = [currentDirectory URLByAppendingPathComponent:classNameH]; NSURL *implementationURL = [currentDirectory URLByAppendingPathComponent:classNameM]; - // uber mode generates classes, so we cannot reorder the lines of generated code [self.interfaceContents sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1 compare:obj2]; }]; @@ -141,8 +155,7 @@ - (void)writeOutputFiles; NSMutableString *interface = [NSMutableString stringWithFormat:@"//\n// This file is generated from %@ by %@.\n// Please do not edit.\n//\n\n#import \n\n\n", self.inputURL.lastPathComponent, self.toolName]; - NSArray *uniqueImports = [[NSSet setWithArray:self.interfaceImports] allObjects]; // removes duplicate imports - for (NSString *import in uniqueImports) { + for (NSString *import in self.interfaceImports) { [interface appendFormat:@"#import %@\n", import]; } [interface appendString:@"\n"]; @@ -203,7 +216,7 @@ - (NSString *)methodNameForKey:(NSString *)key; @implementation CGUClass -- (instancetype)init +- (instancetype)init; { self = [super init]; if (self) { @@ -212,7 +225,8 @@ - (instancetype)init return self; } -- (void)sortMethods { +- (void)sortMethods; +{ [self.methods sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { CGUMethod *method1 = obj1; CGUMethod *method2 = obj2; @@ -229,7 +243,8 @@ - (void)sortMethods { }]; } -- (NSString *)interfaceCode { +- (NSString *)interfaceCode; +{ if (self.methods.count == 0 && self.classType != CGUClassType_Definition) { // no need to print a category/extension if it has no methods return @""; @@ -251,7 +266,8 @@ - (NSString *)interfaceCode { return result; } -- (NSString *)implementationCode { +- (NSString *)implementationCode; +{ if (self.methods.count == 0 && self.classType != CGUClassType_Definition) { // no need to print a category/extension if it has no methods return @""; @@ -273,7 +289,8 @@ - (NSString *)implementationCode { return result; } -- (CGUClassType)classType { +- (CGUClassType)classType; +{ if (self.superClassName) { return CGUClassType_Definition; } else { @@ -287,11 +304,13 @@ - (CGUClassType)classType { @implementation CGUMethod -- (NSString *)interfaceCode { +- (NSString *)interfaceCode; +{ return [NSString stringWithFormat:@"%@ (%@)%@;", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments]; } -- (NSString *)implementationCode { +- (NSString *)implementationCode; +{ // TODO: indent each line in the body? return [NSString stringWithFormat:@"%@ (%@)%@ {\n%@\n}", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments, self.body]; } diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index d03caa2..4bf3c2e 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -32,14 +32,16 @@ - (NSString *)IDS_camelcaseString; return output; } -- (NSString *)IDS_stringWithSuffix:(NSString *)suffix { +- (NSString *)IDS_stringWithSuffix:(NSString *)suffix; +{ if (![self hasSuffix:suffix]) { return [self stringByAppendingString:suffix]; } return self; } -- (NSString *)IDS_asPrefixOf:(NSString *)suffix { +- (NSString *)IDS_asPrefixOf:(NSString *)suffix; +{ if (![suffix hasPrefix:self]) { return [self stringByAppendingString:suffix]; } @@ -48,6 +50,7 @@ - (NSString *)IDS_asPrefixOf:(NSString *)suffix { @end + @implementation IDStoryboardDumper + (NSString *)inputFileExtension; @@ -56,7 +59,7 @@ + (NSString *)inputFileExtension; } /// element is any that have a customClass attribute and contain the valid default class name as their name (e.g. viewController, or tableViewCell) -- (NSString *)classTypeForElement:(NSXMLElement *)element importedCustomClass:(BOOL *)importedCustomClass +- (NSString *)classTypeForElement:(NSXMLElement *)element importedCustomClass:(out BOOL *)importedCustomClass; { if (importedCustomClass) { *importedCustomClass = NO; @@ -77,10 +80,10 @@ - (NSString *)classTypeForElement:(NSXMLElement *)element importedCustomClass:(B } /// You may call this method multiple times with the same className without it having to search the search path each time. It will only search once and cache the result. -- (BOOL)importClass:(NSString *)className +- (BOOL)importClass:(NSString *)className; { /// Keys: NSString of class name; Values: @(BOOL) stating if it was successfully imported or not - if (self.classesImported == nil) { + if (!self.classesImported) { self.classesImported = [NSMutableDictionary dictionary]; } @@ -141,7 +144,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; [identifiers addObjectsFromArray:reuseIdentifiers]; [identifiers addObjectsFromArray:segueIdentifiers]; - self.interfaceImports = [NSMutableArray array]; + self.interfaceImports = [NSMutableSet set]; self.classes = [NSMutableDictionary dictionary]; self.interfaceContents = [NSMutableArray array]; self.implementationContents = [NSMutableArray array]; From 85d9a15750ef7ab6fb5f72cecf788ac649a8805b Mon Sep 17 00:00:00 2001 From: OY Date: Thu, 3 Apr 2014 23:28:15 -0700 Subject: [PATCH 04/13] =?UTF-8?q?=E2=80=9CflagImages=E2=80=9D=20was=20exec?= =?UTF-8?q?uting=20the=20runtime=20hackery=20every=20time=20the=20collecti?= =?UTF-8?q?on=20view=20asked=20for=20a=20cell.=20Now=20it=20only=20compute?= =?UTF-8?q?s=20allImages=20one=20time.=20The=20identifiers=20now=20also=20?= =?UTF-8?q?output=20constraint=20original=20constant=20values,=20which=20i?= =?UTF-8?q?s=20comes=20in=20handy=20when=20using=20auto=20layout.=20Update?= =?UTF-8?q?d=20the=20sample=20app=20to=20show=20where=20this=20may=20be=20?= =?UTF-8?q?useful.=20As=20a=20side=20effect,=20the=20sample=20app=20can=20?= =?UTF-8?q?now=20be=20used=20to=20quiz=20yourself=20on=20the=20country=20f?= =?UTF-8?q?lags:=20tap=20a=20flag,=20think=20of=20the=20country=20name,=20?= =?UTF-8?q?then=20tap=20to=20see=20if=20you=20were=20correct.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CodeGenTestApp/Base.lproj/Main.storyboard | 34 +++++++++++++++++- CodeGenTestApp/CGTADetailViewController.h | 1 + CodeGenTestApp/CGTADetailViewController.m | 28 +++++++++++++++ .../CGTAImagesCatalog+RuntimeHackery.h | 1 + .../CGTAImagesCatalog+RuntimeHackery.m | 22 +++++++++++- CodeGenTestApp/CGTAMasterViewController.h | 1 + CodeGenTestApp/CGTAMasterViewController.m | 35 +++++++++++++------ CodeGenTestApp/MoreExamples.storyboard | 33 ++++++++++++++++- identifierconstants/IDStoryboardDumper.m | 24 +++++++++++-- 9 files changed, 162 insertions(+), 17 deletions(-) diff --git a/CodeGenTestApp/Base.lproj/Main.storyboard b/CodeGenTestApp/Base.lproj/Main.storyboard index 32a3c38..04889e2 100644 --- a/CodeGenTestApp/Base.lproj/Main.storyboard +++ b/CodeGenTestApp/Base.lproj/Main.storyboard @@ -80,18 +80,50 @@ - + + + + + + + + + + + + + + + + + + + + + diff --git a/CodeGenTestApp/CGTADetailViewController.h b/CodeGenTestApp/CGTADetailViewController.h index 5141430..8a9424f 100644 --- a/CodeGenTestApp/CGTADetailViewController.h +++ b/CodeGenTestApp/CGTADetailViewController.h @@ -11,5 +11,6 @@ @interface CGTADetailViewController : UIViewController @property (nonatomic, strong) UIImage *image; +@property (nonatomic, copy) NSString *countryName; @end diff --git a/CodeGenTestApp/CGTADetailViewController.m b/CodeGenTestApp/CGTADetailViewController.m index 0c8c741..2cc3ae9 100644 --- a/CodeGenTestApp/CGTADetailViewController.m +++ b/CodeGenTestApp/CGTADetailViewController.m @@ -9,11 +9,15 @@ #import "CGTADetailViewController.h" #import "CGTATestAppColorList.h" +#import "CGTAMainStoryboardIdentifiers.h" @interface CGTADetailViewController () @property (nonatomic, strong) IBOutlet UIImageView *imageView; +@property (weak, nonatomic) IBOutlet UILabel *tapLabel; +@property (weak, nonatomic) IBOutlet UILabel *countryNameLabel; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *countryNameTopConstraint; @end @@ -26,6 +30,12 @@ - (void)setImage:(UIImage *)image; [self updateView]; } +- (void)setCountryName:(NSString *)countryName; +{ + _countryName = countryName; + [self updateView]; +} + - (void)viewDidLoad; { [self updateView]; @@ -37,11 +47,29 @@ - (void)viewDidLoad; layer.colors = @[(id)[UIColor whiteColor].CGColor, (id)[CGTATestAppColorList tealColor].CGColor]; layer.frame = self.view.layer.bounds; [self.view.layer insertSublayer:layer atIndex:0]; + + // hide the label at first + self.countryNameTopConstraint.constant = 0; } - (void)updateView; { self.imageView.image = self.image; + self.countryNameLabel.text = self.countryName; +} + +- (IBAction)imageTapped:(UITapGestureRecognizer *)sender; +{ + if (sender.state == UIGestureRecognizerStateEnded) { + // the label was positioned perfectly via the storyboard, so now we can restore + // the perfect positioning easily, by refering to the constant that was generated for us! + self.countryNameTopConstraint.constant = self.countryNameTopConstraint.constant == 0 ? [self countryNameTopConstraintOriginalConstant] : 0; + [UIView animateWithDuration:0.2 + animations:^{ + self.tapLabel.alpha = self.countryNameTopConstraint.constant == 0 ? 1 : 0; + [self.view layoutIfNeeded]; + }]; + } } @end diff --git a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.h b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.h index b9c1bb8..74838e6 100644 --- a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.h +++ b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.h @@ -11,6 +11,7 @@ @interface CGTAImagesCatalog (RuntimeHackery) ++ (NSArray *)allImageNames; + (NSArray *)allImages; @end diff --git a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m index c03380f..c722ab7 100644 --- a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m +++ b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m @@ -15,13 +15,33 @@ @implementation CGTAImagesCatalog (RuntimeHackery) ++ (NSArray *)allImageNames; +{ + NSMutableArray *imageNames = [NSMutableArray array]; + unsigned int count; + Method *methods = class_copyMethodList(object_getClass(self), &count); + for (unsigned int index = 0; index < count; index++) { + SEL methodName = method_getName(methods[index]); + if (sel_isEqual(methodName, _cmd) || sel_isEqual(methodName, @selector(allImages))) { + continue; + } + NSString *imageName = NSStringFromSelector(method_getName(methods[index])); + // remove the "Image" suffix + imageName = [imageName substringToIndex:[imageName length] - [@"Image" length]]; + [imageNames addObject:[imageName uppercaseString]]; + } + free(methods); + return imageNames; +} + + (NSArray *)allImages; { NSMutableArray *images = [NSMutableArray array]; unsigned int count; Method *methods = class_copyMethodList(object_getClass(self), &count); for (unsigned int index = 0; index < count; index++) { - if (sel_isEqual(method_getName(methods[index]), _cmd)) { + SEL methodName = method_getName(methods[index]); + if (sel_isEqual(methodName, _cmd) || sel_isEqual(methodName, @selector(allImageNames))) { continue; } id image = method_invoke(self, methods[index]); diff --git a/CodeGenTestApp/CGTAMasterViewController.h b/CodeGenTestApp/CGTAMasterViewController.h index 7339131..0202f2e 100644 --- a/CodeGenTestApp/CGTAMasterViewController.h +++ b/CodeGenTestApp/CGTAMasterViewController.h @@ -10,6 +10,7 @@ @interface CGTAFlagCollectionViewCell : UICollectionViewCell @property (nonatomic, weak) IBOutlet UIImageView *imageView; +@property (nonatomic, copy) NSString *countryName; @end diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index 7a5e6e7..5429081 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -16,6 +16,7 @@ @interface CGTAMasterViewController () @property (nonatomic, weak) IBOutlet UISlider *cellSizeSlider; @property (nonatomic, strong) NSArray *flagImages; +@property (nonatomic, strong) NSArray *flagImageNames; @end @@ -35,7 +36,9 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; { if ([segue.identifier isEqualToString:[self tapOnFlagSegueIdentifier]]) { CGTADetailViewController *detailViewController = segue.destinationViewController; - detailViewController.image = ((CGTAFlagCollectionViewCell *)sender).imageView.image ?: [CGTAImagesCatalog usaImage]; + CGTAFlagCollectionViewCell *cellSender = sender; + detailViewController.image = cellSender.imageView.image; + detailViewController.countryName = cellSender.countryName; } } @@ -50,17 +53,26 @@ - (IBAction)sliderValueChanged:(UISlider *)sender; - (NSArray *)flagImages; { - NSArray *allFlagImages = nil; - - // Initial version: full of strings that you have to type correctly! - // Misspell any of these and your app will crash on trying to add `nil` to an array. - allFlagImages = @[[UIImage imageNamed:@"USA"], [UIImage imageNamed:@"Canada"], [UIImage imageNamed:@"UK"], [UIImage imageNamed:@"Australia"]]; - - // New version: get the properly compiler-checked spelling from the image catalog. - allFlagImages = @[[CGTAImagesCatalog usaImage], [CGTAImagesCatalog canadaImage], [CGTAImagesCatalog ukImage], [CGTAImagesCatalog australiaImage]]; + if (!_flagImages) { + // Initial version: full of strings that you have to type correctly! + // Misspell any of these and your app will crash on trying to add `nil` to an array. + _flagImages = @[[UIImage imageNamed:@"USA"], [UIImage imageNamed:@"Canada"], [UIImage imageNamed:@"UK"], [UIImage imageNamed:@"Australia"]]; + + // New version: get the properly compiler-checked spelling from the image catalog. + _flagImages = @[[CGTAImagesCatalog usaImage], [CGTAImagesCatalog canadaImage], [CGTAImagesCatalog ukImage], [CGTAImagesCatalog australiaImage]]; + + // But really, why not use a little runtime hackery because we can? + _flagImages = [CGTAImagesCatalog allImages]; + } + return _flagImages; +} - // But really, why not use a little runtime hackery because we can? - return [CGTAImagesCatalog allImages]; +- (NSArray *)flagImageNames; +{ + if (!_flagImageNames) { + _flagImageNames = [CGTAImagesCatalog allImageNames]; + } + return _flagImageNames; } #pragma mark - UICollectionViewDataSource @@ -86,6 +98,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell cell = [self dequeueImageCellForIndexPath:indexPath ofCollectionView:collectionView]; cell.imageView.image = self.flagImages[indexPath.item]; + cell.countryName = self.flagImageNames[indexPath.item]; return cell; } diff --git a/CodeGenTestApp/MoreExamples.storyboard b/CodeGenTestApp/MoreExamples.storyboard index 78e11a5..638877b 100644 --- a/CodeGenTestApp/MoreExamples.storyboard +++ b/CodeGenTestApp/MoreExamples.storyboard @@ -411,7 +411,38 @@ Xcode will generate a warning, but everything should still compile. - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 4bf3c2e..72889f7 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -219,9 +219,6 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; } if (importedCustomClass) { - NSArray *segueIdentifiers = [[viewControllerElement nodesForXPath:@".//segue/@identifier" error:&error] valueForKey:NSStringFromSelector(@selector(stringValue))]; - NSArray *reuseIdentifiers = [viewControllerElement nodesForXPath:@".//*[@reuseIdentifier]" error:&error]; - CGUClass *viewControllerClassCategory = self.classes[className]; // we may see the same class twice, so it is storyed in a dictionary if (viewControllerClassCategory == nil) { viewControllerClassCategory = [CGUClass new]; @@ -230,6 +227,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; self.classes[className] = viewControllerClassCategory; } + NSArray *segueIdentifiers = [[viewControllerElement nodesForXPath:@".//segue/@identifier" error:&error] valueForKey:NSStringFromSelector(@selector(stringValue))]; for (NSString *segueIdentifier in segueIdentifiers) { [identifiers removeObject:segueIdentifier]; // we don't want the user accessing this segue via the old method @@ -247,6 +245,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; [viewControllerClassCategory.methods addObject:performSegueMethod]; } + NSArray *reuseIdentifiers = [viewControllerElement nodesForXPath:@".//*[@reuseIdentifier]" error:&error]; for (NSXMLElement *reuseIdentifierElement in reuseIdentifiers) { NSString *reuseIdentifier = [[reuseIdentifierElement attributeForName:@"reuseIdentifier"] stringValue]; [identifiers removeObject:reuseIdentifier]; @@ -283,6 +282,25 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; // TODO: add support for [collectionView dequeueReusableSupplementaryViewOfKind:(NSString *) withReuseIdentifier:(NSString *) forIndexPath:(NSIndexPath *)] } + + // Ex: + NSArray *constraints = [viewControllerElement nodesForXPath:@".//constraint[@constant]" error:NULL]; + for (NSXMLElement *constraint in constraints) { + NSString *constraintId = [[constraint attributeForName:@"id"] stringValue]; + NSXMLElement *node = [[viewControllerElement nodesForXPath:[NSString stringWithFormat:@"./connections/outlet[@destination='%@']", constraintId] error:NULL] firstObject]; + if (node) { + // Ex: + NSString *propertyName = [[node attributeForName:@"property"] stringValue]; + CGFloat constant = [[[constraint attributeForName:@"constant"] stringValue] floatValue]; + + // ouptut - (CGFloat)[(MYCustomViewController *) myCustomConstraintOriginalConstant] + CGUMethod *constraintMethod = [CGUMethod new]; + constraintMethod.returnType = @"CGFloat"; + constraintMethod.nameAndArguments = [NSString stringWithFormat:@"%@OriginalConstant", [[propertyName IDS_camelcaseString] IDS_stringWithSuffix:@"Constraint"]]; + constraintMethod.body = [NSString stringWithFormat:@" return %f;", constant]; + [viewControllerClassCategory.methods addObject:constraintMethod]; + } + } } } From eeed36e11293fd4d80aa781dfa8f593f2ac69b6d Mon Sep 17 00:00:00 2001 From: OY Date: Fri, 11 Apr 2014 10:39:02 -0700 Subject: [PATCH 05/13] =?UTF-8?q?Significantly=20improved=20execution=20ti?= =?UTF-8?q?me=20especially=20for=20large=20storyboard=20files.=20In=20orde?= =?UTF-8?q?r=20to=20determine=20if=20a=20class=20can=20be=20imported,=20it?= =?UTF-8?q?=20now=20looks=20for=20a=20file=20of=20the=20same=20name=20as?= =?UTF-8?q?=20the=20class=20(e.g.=20MyCustomClass.h).=20While=20this=20isn?= =?UTF-8?q?=E2=80=99t=20perfect,=20it=20does=20seem=20to=20handle=20most?= =?UTF-8?q?=20of=20the=20cases=20at=20a=20significant=20performance=20boos?= =?UTF-8?q?t.=20In=20order=20to=20show=20how=20dequeueImageCellForIndexPat?= =?UTF-8?q?h:ofCollectionView:=20can=20return=20a=20CGTAFlagCollectionView?= =?UTF-8?q?Cell=20instance,=20the=20demo=20app=20has=20been=20updated=20to?= =?UTF-8?q?=20separate=20CGTAFlagCollectionViewCell=20into=20its=20own=20f?= =?UTF-8?q?ile.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CodeGenTestApp/CGTAFlagCollectionViewCell.h | 8 ++++++ CodeGenTestApp/CGTAFlagCollectionViewCell.m | 4 +++ CodeGenTestApp/CGTAMasterViewController.h | 6 ----- CodeGenTestApp/CGTAMasterViewController.m | 5 +--- .../CodeGenTestApp.xcodeproj/project.pbxproj | 6 +++++ Shared/CGUCodeGenTool.h | 2 +- Shared/CGUCodeGenTool.m | 10 ++++--- identifierconstants/IDStoryboardDumper.m | 27 +++---------------- 8 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 CodeGenTestApp/CGTAFlagCollectionViewCell.h create mode 100644 CodeGenTestApp/CGTAFlagCollectionViewCell.m diff --git a/CodeGenTestApp/CGTAFlagCollectionViewCell.h b/CodeGenTestApp/CGTAFlagCollectionViewCell.h new file mode 100644 index 0000000..6d4a9b7 --- /dev/null +++ b/CodeGenTestApp/CGTAFlagCollectionViewCell.h @@ -0,0 +1,8 @@ +#import + +@interface CGTAFlagCollectionViewCell : UICollectionViewCell + +@property (nonatomic, weak) IBOutlet UIImageView *imageView; +@property (nonatomic, copy) NSString *countryName; + +@end diff --git a/CodeGenTestApp/CGTAFlagCollectionViewCell.m b/CodeGenTestApp/CGTAFlagCollectionViewCell.m new file mode 100644 index 0000000..d529067 --- /dev/null +++ b/CodeGenTestApp/CGTAFlagCollectionViewCell.m @@ -0,0 +1,4 @@ +#import "CGTAFlagCollectionViewCell.h" + +@implementation CGTAFlagCollectionViewCell +@end \ No newline at end of file diff --git a/CodeGenTestApp/CGTAMasterViewController.h b/CodeGenTestApp/CGTAMasterViewController.h index 0202f2e..63a5eab 100644 --- a/CodeGenTestApp/CGTAMasterViewController.h +++ b/CodeGenTestApp/CGTAMasterViewController.h @@ -7,12 +7,6 @@ // See the LICENSE file distributed with this work for the terms under // which Square, Inc. licenses this file to you. -@interface CGTAFlagCollectionViewCell : UICollectionViewCell - -@property (nonatomic, weak) IBOutlet UIImageView *imageView; -@property (nonatomic, copy) NSString *countryName; - -@end @interface CGTAMasterViewController : UICollectionViewController @end diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index 5429081..61de2b9 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -11,6 +11,7 @@ #import "CGTADetailViewController.h" #import "CGTAImagesCatalog+RuntimeHackery.h" #import "CGTAMainStoryboardIdentifiers.h" +#import "CGTAFlagCollectionViewCell.h" @interface CGTAMasterViewController () @@ -103,7 +104,3 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell } @end - - -@implementation CGTAFlagCollectionViewCell -@end diff --git a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj index 9f134bc..f0d3449 100644 --- a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj +++ b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ A881854218A9B622002803FC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A83878E518A0367C00B386D6 /* main.m */; }; A881854418A9B663002803FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A83878EB18A0367C00B386D6 /* Main.storyboard */; }; AA24EC5A18EB4F8E00DB0F94 /* MoreExamples.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */; }; + AA509EE318F85CA5000F4136 /* CGTAFlagCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */; }; AAA9F41518ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */; }; /* End PBXBuildFile section */ @@ -98,6 +99,8 @@ A881852718A9B520002803FC /* codegenutils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = codegenutils.xcodeproj; path = ../codegenutils.xcodeproj; sourceTree = ""; }; A89D8FE617CFFDCE0077F2B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MoreExamples.storyboard; sourceTree = ""; }; + AA509EE118F85CA5000F4136 /* CGTAFlagCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAFlagCollectionViewCell.h; sourceTree = ""; }; + AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAFlagCollectionViewCell.m; sourceTree = ""; }; AAA9F41318ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAMoreExamplesStoryboardIdentifiers.h; sourceTree = ""; }; AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAMoreExamplesStoryboardIdentifiers.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -128,6 +131,8 @@ A83878EF18A0367C00B386D6 /* CGTAMasterViewController.m */, A83878F118A0367C00B386D6 /* CGTADetailViewController.h */, A83878F218A0367C00B386D6 /* CGTADetailViewController.m */, + AA509EE118F85CA5000F4136 /* CGTAFlagCollectionViewCell.h */, + AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */, A838791718A0456500B386D6 /* Derived Sources */, A83878E018A0367C00B386D6 /* Supporting Files */, ); @@ -323,6 +328,7 @@ A881853C18A9B622002803FC /* CGTAMasterViewController.m in Sources */, A881853F18A9B622002803FC /* CGTAMainStoryboardIdentifiers.m in Sources */, A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */, + AA509EE318F85CA5000F4136 /* CGTAFlagCollectionViewCell.m in Sources */, A881853E18A9B622002803FC /* CGTAImagesCatalog.m in Sources */, A881854018A9B622002803FC /* CGTATestAppColorList.m in Sources */, A881854218A9B622002803FC /* main.m in Sources */, diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index 265be44..eb4687f 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -18,7 +18,7 @@ @property (copy) NSURL *inputURL; @property (copy) NSString *classPrefix; -@property (copy) NSString *searchPath; +@property (copy) NSSet *headerFilesFound; @property BOOL targetiOS6; @property BOOL skipClassDeclaration; diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index c29968c..3f52b96 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -46,10 +46,10 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; { char opt = -1; NSURL *searchURL = nil; - NSString *searchPath = nil; NSString *classPrefix = @""; BOOL target6 = NO; NSMutableArray *inputURLs = [NSMutableArray array]; + NSMutableSet *headerFilesFound = [NSMutableSet set]; while ((opt = getopt(argc, (char *const*)argv, "o:f:p:h6")) != -1) { switch (opt) { @@ -74,7 +74,7 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; } case 'f': { - searchPath = [[NSString alloc] initWithUTF8String:optarg]; + NSString *searchPath = [[NSString alloc] initWithUTF8String:optarg]; searchPath = [searchPath stringByExpandingTildeInPath]; searchURL = [NSURL fileURLWithPath:searchPath]; break; @@ -107,6 +107,10 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; if ([url.pathExtension isEqualToString:[self inputFileExtension]]) { [inputURLs addObject:url]; } + if ([url.pathExtension isEqualToString:@"h"]) { + NSString *fileName = [url lastPathComponent]; + [headerFilesFound addObject:[fileName substringToIndex:[fileName length] - 2]]; + } } } @@ -117,7 +121,7 @@ + (int)startWithArgc:(int)argc argv:(const char **)argv; CGUCodeGenTool *target = [self new]; target.inputURL = url; - target.searchPath = searchPath; + target.headerFilesFound = headerFilesFound; target.targetiOS6 = target6; target.classPrefix = classPrefix; target.toolName = [[NSString stringWithUTF8String:argv[0]] lastPathComponent]; diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 72889f7..2a281c6 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -92,31 +92,10 @@ - (BOOL)importClass:(NSString *)className; return [self.classesImported[className] boolValue]; } - NSTask *findFiles = [NSTask new]; - [findFiles setLaunchPath:@"/usr/bin/grep"]; - [findFiles setCurrentDirectoryPath:self.searchPath]; - [findFiles setArguments:[[NSString stringWithFormat:@"-r -l -e @interface[[:space:]]\\{1,\\}%@[[:space:]]*:[[:space:]]*[[:alpha:]]\\{1,\\} .", className] componentsSeparatedByString:@" "]]; - - NSPipe *pipe = [NSPipe pipe]; - [findFiles setStandardOutput:pipe]; - NSFileHandle *file = [pipe fileHandleForReading]; - - [findFiles launch]; - [findFiles waitUntilExit]; - - NSData *data = [file readDataToEndOfFile]; - - NSString *string = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]; - NSArray *lines = [string componentsSeparatedByString:@"\n"]; BOOL successfullyImported = NO; - for (NSString *line in lines) { - NSURL *path = [NSURL URLWithString:line]; - NSString *importFile = [path lastPathComponent]; - if ([importFile hasSuffix:@".h"]) { - [self.interfaceImports addObject:[NSString stringWithFormat:@"\"%@\"", importFile]]; - successfullyImported = YES; - break; - } + if ([self.headerFilesFound containsObject:className]) { + [self.interfaceImports addObject:[NSString stringWithFormat:@"\"%@.h\"", className]]; + successfullyImported = YES; } if (!successfullyImported) { From 7f1228940e5347d41299af39caa64ce078bb43fd Mon Sep 17 00:00:00 2001 From: OY Date: Fri, 11 Apr 2014 18:33:53 -0700 Subject: [PATCH 06/13] =?UTF-8?q?More=20fixes=20according=20to=20Puls?= =?UTF-8?q?=E2=80=99s=20suggestions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m | 9 +++++++-- CodeGenTestApp/CGTAMasterViewController.m | 4 ++-- identifierconstants/IDStoryboardDumper.m | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m index c722ab7..3f83299 100644 --- a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m +++ b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m @@ -15,6 +15,11 @@ @implementation CGTAImagesCatalog (RuntimeHackery) ++ (BOOL)isRuntimeHackeryMethod:(SEL)methodName; +{ + return sel_isEqual(methodName, @selector(allImageNames)) || sel_isEqual(methodName, @selector(allImages)); +} + + (NSArray *)allImageNames; { NSMutableArray *imageNames = [NSMutableArray array]; @@ -22,7 +27,7 @@ + (NSArray *)allImageNames; Method *methods = class_copyMethodList(object_getClass(self), &count); for (unsigned int index = 0; index < count; index++) { SEL methodName = method_getName(methods[index]); - if (sel_isEqual(methodName, _cmd) || sel_isEqual(methodName, @selector(allImages))) { + if ([self isRuntimeHackeryMethod:methodName]) { continue; } NSString *imageName = NSStringFromSelector(method_getName(methods[index])); @@ -41,7 +46,7 @@ + (NSArray *)allImages; Method *methods = class_copyMethodList(object_getClass(self), &count); for (unsigned int index = 0; index < count; index++) { SEL methodName = method_getName(methods[index]); - if (sel_isEqual(methodName, _cmd) || sel_isEqual(methodName, @selector(allImageNames))) { + if ([self isRuntimeHackeryMethod:methodName]) { continue; } id image = method_invoke(self, methods[index]); diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index 61de2b9..7ae03f1 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -55,7 +55,7 @@ - (IBAction)sliderValueChanged:(UISlider *)sender; - (NSArray *)flagImages; { if (!_flagImages) { - // Initial version: full of strings that you have to type correctly! + // What you might have done without this tool: full of strings that you have to type correctly! // Misspell any of these and your app will crash on trying to add `nil` to an array. _flagImages = @[[UIImage imageNamed:@"USA"], [UIImage imageNamed:@"Canada"], [UIImage imageNamed:@"UK"], [UIImage imageNamed:@"Australia"]]; @@ -92,7 +92,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell { CGTAFlagCollectionViewCell *cell = nil; - // Initial version: we must type in the identifier, and have no guarantees as to which class it returns + // What you might have done without this tool: we must type in the identifier, and have no guarantees as to which class it returns cell = (CGTAFlagCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"Image Cell" forIndexPath:indexPath]; // New version: class extension which returns the exact type we are expecting diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 2a281c6..224fdc5 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -10,6 +10,7 @@ #import "IDStoryboardDumper.h" @interface IDStoryboardDumper () +/// Keys: NSString of class name; Values: @(BOOL) stating if it was successfully imported or not @property (strong) NSMutableDictionary *classesImported; @end @@ -82,7 +83,6 @@ - (NSString *)classTypeForElement:(NSXMLElement *)element importedCustomClass:(o /// You may call this method multiple times with the same className without it having to search the search path each time. It will only search once and cache the result. - (BOOL)importClass:(NSString *)className; { - /// Keys: NSString of class name; Values: @(BOOL) stating if it was successfully imported or not if (!self.classesImported) { self.classesImported = [NSMutableDictionary dictionary]; } From 987c62106ead77bb033dcf7c56559bc88d86f859 Mon Sep 17 00:00:00 2001 From: OY Date: Sat, 12 Apr 2014 12:00:21 -0700 Subject: [PATCH 07/13] =?UTF-8?q?Fixed=20bug=20where=20it=20was=20attempti?= =?UTF-8?q?ng=20to=20build=20an=20image=20with=20the=20=E2=80=98isRuntimeH?= =?UTF-8?q?ackeryMethod=E2=80=99=20name.=20Added=20a=20boolean=20property?= =?UTF-8?q?=20to=20prevent=20the=20need=20for=20float=20comparison.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CodeGenTestApp/CGTADetailViewController.m | 37 ++++++++++++++----- .../CGTAImagesCatalog+RuntimeHackery.m | 4 +- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CodeGenTestApp/CGTADetailViewController.m b/CodeGenTestApp/CGTADetailViewController.m index 2cc3ae9..625fade 100644 --- a/CodeGenTestApp/CGTADetailViewController.m +++ b/CodeGenTestApp/CGTADetailViewController.m @@ -18,6 +18,7 @@ @interface CGTADetailViewController () @property (weak, nonatomic) IBOutlet UILabel *tapLabel; @property (weak, nonatomic) IBOutlet UILabel *countryNameLabel; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *countryNameTopConstraint; +@property (nonatomic) BOOL countryNameVisible; @end @@ -36,6 +37,30 @@ - (void)setCountryName:(NSString *)countryName; [self updateView]; } +- (void)setCountryNameVisible:(BOOL)countryNameVisible; +{ + [self setCountryNameVisible:countryNameVisible animated:NO]; +} + +- (void)setCountryNameVisible:(BOOL)countryNameVisible animated:(BOOL)animated; +{ + _countryNameVisible = countryNameVisible; + + // the label was positioned perfectly via the storyboard, so now we can restore + // this positioning simply by refering to the constant that was generated for us! + self.countryNameTopConstraint.constant = countryNameVisible ? [self countryNameTopConstraintOriginalConstant] : 0; + + if (animated) { + [UIView animateWithDuration:0.2 + animations:^{ + self.tapLabel.alpha = countryNameVisible ? 0 : 1; + [self.view layoutIfNeeded]; + }]; + } else { + self.tapLabel.alpha = countryNameVisible ? 0 : 1; + } +} + - (void)viewDidLoad; { [self updateView]; @@ -48,8 +73,7 @@ - (void)viewDidLoad; layer.frame = self.view.layer.bounds; [self.view.layer insertSublayer:layer atIndex:0]; - // hide the label at first - self.countryNameTopConstraint.constant = 0; + self.countryNameVisible = NO; } - (void)updateView; @@ -61,14 +85,7 @@ - (void)updateView; - (IBAction)imageTapped:(UITapGestureRecognizer *)sender; { if (sender.state == UIGestureRecognizerStateEnded) { - // the label was positioned perfectly via the storyboard, so now we can restore - // the perfect positioning easily, by refering to the constant that was generated for us! - self.countryNameTopConstraint.constant = self.countryNameTopConstraint.constant == 0 ? [self countryNameTopConstraintOriginalConstant] : 0; - [UIView animateWithDuration:0.2 - animations:^{ - self.tapLabel.alpha = self.countryNameTopConstraint.constant == 0 ? 1 : 0; - [self.view layoutIfNeeded]; - }]; + [self setCountryNameVisible:!self.countryNameVisible animated:YES]; } } diff --git a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m index 3f83299..2206f66 100644 --- a/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m +++ b/CodeGenTestApp/CGTAImagesCatalog+RuntimeHackery.m @@ -17,7 +17,9 @@ @implementation CGTAImagesCatalog (RuntimeHackery) + (BOOL)isRuntimeHackeryMethod:(SEL)methodName; { - return sel_isEqual(methodName, @selector(allImageNames)) || sel_isEqual(methodName, @selector(allImages)); + return sel_isEqual(methodName, @selector(isRuntimeHackeryMethod:)) || + sel_isEqual(methodName, @selector(allImageNames)) || + sel_isEqual(methodName, @selector(allImages)); } + (NSArray *)allImageNames; From 61fb2e5f2b171ecb4763196d05443d5dce6dc4f3 Mon Sep 17 00:00:00 2001 From: OY Date: Fri, 9 May 2014 15:17:56 -0700 Subject: [PATCH 08/13] Added support for dequeuing reusable collection views. Now handles duplicate identifiers by removing them. --- .../Base.lproj/EvenMoreExamples.storyboard | 161 +++ .../Base.lproj/MoreExamples.storyboard | 1228 +++++++++++++++++ CodeGenTestApp/CGTATableViewController.h | 5 + CodeGenTestApp/CGTATableViewController.m | 10 + .../CodeGenTestApp.xcodeproj/project.pbxproj | 40 +- CodeGenTestApp/MoreExamples.storyboard | 510 ------- Shared/CGUCodeGenTool.h | 3 + Shared/CGUCodeGenTool.m | 15 + identifierconstants/IDStoryboardDumper.m | 58 +- 9 files changed, 1507 insertions(+), 523 deletions(-) create mode 100644 CodeGenTestApp/Base.lproj/EvenMoreExamples.storyboard create mode 100644 CodeGenTestApp/Base.lproj/MoreExamples.storyboard create mode 100644 CodeGenTestApp/CGTATableViewController.h create mode 100644 CodeGenTestApp/CGTATableViewController.m delete mode 100644 CodeGenTestApp/MoreExamples.storyboard diff --git a/CodeGenTestApp/Base.lproj/EvenMoreExamples.storyboard b/CodeGenTestApp/Base.lproj/EvenMoreExamples.storyboard new file mode 100644 index 0000000..ab8f478 --- /dev/null +++ b/CodeGenTestApp/Base.lproj/EvenMoreExamples.storyboard @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CodeGenTestApp/Base.lproj/MoreExamples.storyboard b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard new file mode 100644 index 0000000..a152318 --- /dev/null +++ b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CodeGenTestApp/CGTATableViewController.h b/CodeGenTestApp/CGTATableViewController.h new file mode 100644 index 0000000..b23fc8e --- /dev/null +++ b/CodeGenTestApp/CGTATableViewController.h @@ -0,0 +1,5 @@ +#import + +@interface CGTATableViewController : UITableViewController + +@end diff --git a/CodeGenTestApp/CGTATableViewController.m b/CodeGenTestApp/CGTATableViewController.m new file mode 100644 index 0000000..bc788f4 --- /dev/null +++ b/CodeGenTestApp/CGTATableViewController.m @@ -0,0 +1,10 @@ +#import "CGTATableViewController.h" + +@interface CGTATableViewController () + +@end + +@implementation CGTATableViewController + + +@end diff --git a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj index f0d3449..f0c22bb 100644 --- a/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj +++ b/CodeGenTestApp/CodeGenTestApp.xcodeproj/project.pbxproj @@ -21,8 +21,11 @@ A881854118A9B622002803FC /* CGTAImagesCatalog+RuntimeHackery.m in Sources */ = {isa = PBXBuildFile; fileRef = A838793518A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.m */; }; A881854218A9B622002803FC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A83878E518A0367C00B386D6 /* main.m */; }; A881854418A9B663002803FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A83878EB18A0367C00B386D6 /* Main.storyboard */; }; - AA24EC5A18EB4F8E00DB0F94 /* MoreExamples.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */; }; + AA08DDC21914774A00141224 /* CGTATableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA08DDC11914774A00141224 /* CGTATableViewController.m */; }; AA509EE318F85CA5000F4136 /* CGTAFlagCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */; }; + AA520FA6191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AA520FA5191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.m */; }; + AA72FDCA191D506E0092C2DA /* EvenMoreExamples.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA72FDC8191D506E0092C2DA /* EvenMoreExamples.storyboard */; }; + AA72FDCD191D509D0092C2DA /* MoreExamples.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA72FDCB191D509D0092C2DA /* MoreExamples.storyboard */; }; AAA9F41518ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m in Sources */ = {isa = PBXBuildFile; fileRef = AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */; }; /* End PBXBuildFile section */ @@ -98,9 +101,14 @@ A881852518A9B512002803FC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; A881852718A9B520002803FC /* codegenutils.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = codegenutils.xcodeproj; path = ../codegenutils.xcodeproj; sourceTree = ""; }; A89D8FE617CFFDCE0077F2B5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MoreExamples.storyboard; sourceTree = ""; }; + AA08DDC01914774A00141224 /* CGTATableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTATableViewController.h; sourceTree = ""; }; + AA08DDC11914774A00141224 /* CGTATableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTATableViewController.m; sourceTree = ""; }; AA509EE118F85CA5000F4136 /* CGTAFlagCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAFlagCollectionViewCell.h; sourceTree = ""; }; AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAFlagCollectionViewCell.m; sourceTree = ""; }; + AA520FA4191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAEvenMoreExamplesStoryboardIdentifiers.h; sourceTree = ""; }; + AA520FA5191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAEvenMoreExamplesStoryboardIdentifiers.m; sourceTree = ""; }; + AA72FDC9191D506E0092C2DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/EvenMoreExamples.storyboard; sourceTree = ""; }; + AA72FDCC191D509D0092C2DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MoreExamples.storyboard; sourceTree = ""; }; AAA9F41318ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CGTAMoreExamplesStoryboardIdentifiers.h; sourceTree = ""; }; AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CGTAMoreExamplesStoryboardIdentifiers.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -125,7 +133,8 @@ A83878E818A0367C00B386D6 /* CGTAAppDelegate.h */, A83878E918A0367C00B386D6 /* CGTAAppDelegate.m */, A83878EB18A0367C00B386D6 /* Main.storyboard */, - AA24EC5918EB4F8E00DB0F94 /* MoreExamples.storyboard */, + AA72FDCB191D509D0092C2DA /* MoreExamples.storyboard */, + AA72FDC8191D506E0092C2DA /* EvenMoreExamples.storyboard */, A83878F418A0367C00B386D6 /* Images.xcassets */, A83878EE18A0367C00B386D6 /* CGTAMasterViewController.h */, A83878EF18A0367C00B386D6 /* CGTAMasterViewController.m */, @@ -133,6 +142,8 @@ A83878F218A0367C00B386D6 /* CGTADetailViewController.m */, AA509EE118F85CA5000F4136 /* CGTAFlagCollectionViewCell.h */, AA509EE218F85CA5000F4136 /* CGTAFlagCollectionViewCell.m */, + AA08DDC01914774A00141224 /* CGTATableViewController.h */, + AA08DDC11914774A00141224 /* CGTATableViewController.m */, A838791718A0456500B386D6 /* Derived Sources */, A83878E018A0367C00B386D6 /* Supporting Files */, ); @@ -159,6 +170,8 @@ A838793218A0557E00B386D6 /* CGTAMainStoryboardIdentifiers.m */, AAA9F41318ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.h */, AAA9F41418ED1A0C00BA7A27 /* CGTAMoreExamplesStoryboardIdentifiers.m */, + AA520FA4191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.h */, + AA520FA5191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.m */, A838791818A04AB300B386D6 /* CGTATestAppColorList.h */, A838791918A04AB300B386D6 /* CGTATestAppColorList.m */, A838793418A05B6D00B386D6 /* CGTAImagesCatalog+RuntimeHackery.h */, @@ -293,8 +306,9 @@ buildActionMask = 2147483647; files = ( A881854418A9B663002803FC /* Main.storyboard in Resources */, - AA24EC5A18EB4F8E00DB0F94 /* MoreExamples.storyboard in Resources */, + AA72FDCA191D506E0092C2DA /* EvenMoreExamples.storyboard in Resources */, A881853A18A9B614002803FC /* Images.xcassets in Resources */, + AA72FDCD191D509D0092C2DA /* MoreExamples.storyboard in Resources */, A83878E418A0367C00B386D6 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -331,7 +345,9 @@ AA509EE318F85CA5000F4136 /* CGTAFlagCollectionViewCell.m in Sources */, A881853E18A9B622002803FC /* CGTAImagesCatalog.m in Sources */, A881854018A9B622002803FC /* CGTATestAppColorList.m in Sources */, + AA520FA6191D7D5E005C913E /* CGTAEvenMoreExamplesStoryboardIdentifiers.m in Sources */, A881854218A9B622002803FC /* main.m in Sources */, + AA08DDC21914774A00141224 /* CGTATableViewController.m in Sources */, A881853D18A9B622002803FC /* CGTADetailViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -373,6 +389,22 @@ name = Main.storyboard; sourceTree = ""; }; + AA72FDC8191D506E0092C2DA /* EvenMoreExamples.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AA72FDC9191D506E0092C2DA /* Base */, + ); + name = EvenMoreExamples.storyboard; + sourceTree = ""; + }; + AA72FDCB191D509D0092C2DA /* MoreExamples.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AA72FDCC191D509D0092C2DA /* Base */, + ); + name = MoreExamples.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/CodeGenTestApp/MoreExamples.storyboard b/CodeGenTestApp/MoreExamples.storyboard deleted file mode 100644 index 638877b..0000000 --- a/CodeGenTestApp/MoreExamples.storyboard +++ /dev/null @@ -1,510 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index eb4687f..014848f 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -68,6 +68,9 @@ @property (copy) NSString *nameAndArguments; @property (copy) NSString *body; +// E.g. "doSomethingWithString:andNumber:" +@property (readonly) NSString *name; + - (NSString *)interfaceCode; - (NSString *)implementationCode; diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 3f52b96..3050992 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -308,6 +308,21 @@ - (CGUClassType)classType; @implementation CGUMethod +- (NSString *)name; +{ + if ([self.nameAndArguments rangeOfString:@":"].location == NSNotFound) { + return self.nameAndArguments; + } else { + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\w+:" options:0 error:NULL]; + NSArray *matches = [regex matchesInString:self.nameAndArguments options:0 range:NSMakeRange(0, [self.nameAndArguments length])]; + NSMutableString *name = [NSMutableString string]; + for (NSTextCheckingResult *match in matches) { + [name appendString:[self.nameAndArguments substringWithRange:match.range]]; + } + return name; + } +} + - (NSString *)interfaceCode; { return [NSString stringWithFormat:@"%@ (%@)%@;", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments]; diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 224fdc5..d97fcc5 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -54,6 +54,39 @@ - (NSString *)IDS_asPrefixOf:(NSString *)suffix; @implementation IDStoryboardDumper +- (void)removeDuplicatedExtensionMethods; +{ + // When there are duplicate category methods, the behavior is undefined. Therefore, we want to remove any duplicates. + + /// Stores all the methods that were added as NSStrings in the form "_" (e.g. "MYViewController_dequeueMYSampleCellForIndexPath:"). + static NSMutableSet *classCategoryMethodsAdded = nil; + if (classCategoryMethodsAdded == nil) { + classCategoryMethodsAdded = [NSMutableSet set]; + } + + for (NSString *className in self.classes) { + CGUClass *class = self.classes[className]; + NSMutableArray *methodsToRemove = [NSMutableArray array]; + NSMutableSet *methodNamesRemoved = [NSMutableSet set]; + for (CGUMethod *method in class.methods) { + NSString *methodName = method.name; + NSString *classMethodKey = [NSString stringWithFormat:@"%@_%@", className, methodName]; + if ([classCategoryMethodsAdded containsObject:classMethodKey]) { + [methodsToRemove addObject:method]; + [methodNamesRemoved addObject:methodName]; + } else { + [classCategoryMethodsAdded addObject:classMethodKey]; + } + } + + if ([methodNamesRemoved count] > 0) { + NSLog(@"Warning: Found (and removed) %lu duplicate identifier(s) associated with class %@. These are the methods that would have been duplicated: %@.", (unsigned long)[methodNamesRemoved count], className, [[methodNamesRemoved allObjects] componentsJoinedByString:@", "]); + } + + [class.methods removeObjectsInArray:methodsToRemove]; + } +} + + (NSString *)inputFileExtension; { return @"storyboard"; @@ -198,7 +231,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; } if (importedCustomClass) { - CGUClass *viewControllerClassCategory = self.classes[className]; // we may see the same class twice, so it is storyed in a dictionary + CGUClass *viewControllerClassCategory = self.classes[className]; // we may see the same class twice, so it is stored in a dictionary if (viewControllerClassCategory == nil) { viewControllerClassCategory = [CGUClass new]; viewControllerClassCategory.name = className; @@ -237,29 +270,34 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; [viewControllerClassCategory.methods addObject:reuseIdentifierMethod]; NSString *elementName = reuseIdentifierElement.name; // E.g. collectionViewCell, tableViewCell, etc. - NSString *methodNameSecondArgument = nil; + NSString *additionalMethodArguments = nil; + NSString *suffix = nil; NSString *code = nil; if ([elementName isEqualToString:@"tableViewCell"]) { - methodNameSecondArgument = @"ofTableView:(UITableView *)tableView"; + suffix = @"Cell"; + additionalMethodArguments = @"ofTableView:(UITableView *)tableView"; code = [NSString stringWithFormat:@"[tableView dequeueReusableCellWithIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; - } else if ([elementName isEqualToString:@"collectionViewCell"] || [elementName isEqualToString:@"collectionReusableView"]) { - methodNameSecondArgument = @"ofCollectionView:(UICollectionView *)collectionView"; + } else if ([elementName isEqualToString:@"collectionViewCell"]) { + suffix = @"Cell"; + additionalMethodArguments = @"ofCollectionView:(UICollectionView *)collectionView"; code = [NSString stringWithFormat:@"[collectionView dequeueReusableCellWithReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; + } else if ([elementName isEqualToString:@"collectionReusableView"]) { + suffix = @"View"; + additionalMethodArguments = @"ofKind:(NSString *)kind ofCollectionView:(UICollectionView *)collectionView"; + code = [NSString stringWithFormat:@"[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; } else { NSLog(@"Warning: Unknown reuse identifier %@.", elementName); continue; } - NSString *reuseIdentifierClassName = [self classTypeForElement:reuseIdentifierElement importedCustomClass:NULL]; + NSString *reuseIdentifierClassName = [self classTypeForElement:reuseIdentifierElement importedCustomClass:NULL]; // E.g. UITableViewCell, UICollectionViewCell, UICollectionReusableView, or custom class // output - (MYCustomCell *)[(MYCustomViewController *) dequeueMyCustomCellForIndexPath:ofTableView:] CGUMethod *dequeueMethod = [CGUMethod new]; dequeueMethod.returnType = [NSString stringWithFormat:@"%@ *", reuseIdentifierClassName]; - dequeueMethod.nameAndArguments = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Cell"], methodNameSecondArgument]; + dequeueMethod.nameAndArguments = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:suffix], additionalMethodArguments]; dequeueMethod.body = [NSString stringWithFormat:@" return %@;", code]; [viewControllerClassCategory.methods addObject:dequeueMethod]; - - // TODO: add support for [collectionView dequeueReusableSupplementaryViewOfKind:(NSString *) withReuseIdentifier:(NSString *) forIndexPath:(NSIndexPath *)] } // Ex: @@ -293,6 +331,8 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; [self.implementationContents addObject:[NSString stringWithFormat:@"NSString *const %@ = @\"%@\";\n", key, uniqueKeys[key]]]; } + [self removeDuplicatedExtensionMethods]; + [self writeOutputFiles]; completionBlock(); } From bc442e34874f92935f2b0593ef5b4d91cc74f2e5 Mon Sep 17 00:00:00 2001 From: OY Date: Mon, 12 May 2014 09:07:05 -0700 Subject: [PATCH 09/13] Updated readme file with new examples. --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31f1fef..93c9f71 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,76 @@ We already fixed the part about code reuse with `objc-colordump`, and now we can Call `objc-identifierconstants` with the `.storyboard` paths as arguments from the directory into which it should output the code. -For a storyboard named "Foo" with view controller identifier "Bar" and segue identifier "Baz" somewhere in it, you'll get `FooStoryboardIdenfitiers.h` and `FooStoryboardIdentifiers.m` with `extern NSString *const FooStoryboardBarIdentifier` and `extern NSString *const FooStoryboardBazIdentifier` in it. Put them in your DerivedSources folder and you're good to go. +For a storyboard named "Foo", you'll get `FooStoryboardIdenfitiers.h` and `FooStoryboardIdentifiers.m`. Put them in your DerivedSources folder and you're good to go. + +The tool will first attempt to add category methods on your existing view controller subclasses. If it does not find a given class, it resorts to outputting constants. For example, a segue identifier "Baz" will generate `extern NSString *const FooStoryboardBazIdentifier`. + +### Examples + +#### Storyboard scenes + +```objective-c +// old way: +UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; +id viewController = [storyboard instantiateViewControllerWithIdentifier:@"Detail View Controller"]; + +// new way: +MYDetailViewController *detailViewController = [MYMainStoryboard instantiateDetailViewController]; +``` + +#### Segues + +```objective-c +// old way: +[self performSegueWithIdentifier:@"Show Details" sender:nil]; + +// new way: +[self performShowDetailsSegue]; +``` + +#### Table view cells + +```objective-c +// old way: +id cell = [tableView dequeueReusableCellWithIdentifier:@"Image cell" forIndexPath:indexPath]; + +// new way: +MYImageCell *imageCell = [self dequeueImageCellForIndexPath:indexPath ofTableView:tableView]; +``` + +#### Auto Layout constraint constants + +```objective-c +// old way: +self.labelHeightConstraint.constant = showLabel ? 40.0f : 0.0f; + +// new way: +self.labelHeightConstraint.constant = showLabel ? [self labelHeightConstraintOriginalConstant] : 0.0f; +``` + +#### Other features + +```objective-c +// getting a storyboard +UIStoryboard *storyboard = [MYCustomStoryboard storyboard]; + +// instantiate initial storyboard view controller +MYCustomViewController *viewController = [MYCustomStoryboard instantiateInitialViewController]; + +// getting segue identifier +NSString *segueID = [self <#segueID#>SegueIdentifier]; + +// getting cell/view identifier (for UITableView or UICollectionView) +NSString *cellID = [self <#cellID#>Identifier]; + +// dequeue collection view reusable cell +MYCustomCell *cell = [self dequeue<#cellID#>CellForIndexPath:indexPath ofCollectionView:collectionView]; + +// dequeue collectiov view reusable view +MYCustomView *view = [self dequeue<#viewID#>ViewForIndexPath:indexPath ofKind:kind ofCollectionView:collectionView]; +``` + +**Note:** in the above examples, `self` refers to a subclass of UIViewController. ## Command-line options (common to all three tools) From 60bd3736cb34d2b6d3003fe2f84bebcf9f82a659 Mon Sep 17 00:00:00 2001 From: OY Date: Tue, 13 May 2014 14:26:53 -0700 Subject: [PATCH 10/13] Removed logic that automatically adds prefixes and suffixes to method names. This gives the user more control over the generated method names. Added constants for scene identifiers. Added a way to generate documentation for generated methods. --- CodeGenTestApp/Base.lproj/Main.storyboard | 4 +- .../Base.lproj/MoreExamples.storyboard | 2 +- README.md | 31 +++++++++---- Shared/CGUCodeGenTool.h | 3 ++ Shared/CGUCodeGenTool.m | 10 ++++- identifierconstants/IDStoryboardDumper.m | 44 +++++++------------ 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/CodeGenTestApp/Base.lproj/Main.storyboard b/CodeGenTestApp/Base.lproj/Main.storyboard index 04889e2..5890d81 100644 --- a/CodeGenTestApp/Base.lproj/Main.storyboard +++ b/CodeGenTestApp/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/CodeGenTestApp/Base.lproj/MoreExamples.storyboard b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard index a152318..35ecc47 100644 --- a/CodeGenTestApp/Base.lproj/MoreExamples.storyboard +++ b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard @@ -1223,6 +1223,6 @@ Xcode will generate a warning, but everything should still compile. - + diff --git a/README.md b/README.md index 93c9f71..2614705 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The tool will first attempt to add category methods on your existing view contro ```objective-c // old way: -UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; +UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; id viewController = [storyboard instantiateViewControllerWithIdentifier:@"Detail View Controller"]; // new way: @@ -75,7 +75,7 @@ MYDetailViewController *detailViewController = [MYMainStoryboard instantiateDeta ```objective-c // old way: -[self performSegueWithIdentifier:@"Show Details" sender:nil]; +[self performSegueWithIdentifier:@"Show Details Segue" sender:nil]; // new way: [self performShowDetailsSegue]; @@ -85,7 +85,7 @@ MYDetailViewController *detailViewController = [MYMainStoryboard instantiateDeta ```objective-c // old way: -id cell = [tableView dequeueReusableCellWithIdentifier:@"Image cell" forIndexPath:indexPath]; +id cell = [tableView dequeueReusableCellWithIdentifier:@"Image Cell" forIndexPath:indexPath]; // new way: MYImageCell *imageCell = [self dequeueImageCellForIndexPath:indexPath ofTableView:tableView]; @@ -107,24 +107,39 @@ self.labelHeightConstraint.constant = showLabel ? [self labelHeightConstraintOri // getting a storyboard UIStoryboard *storyboard = [MYCustomStoryboard storyboard]; +// getting a scene identifier +NSString *sceneIdentifier = [MYCustomStoryboard <#sceneID#>Identifier]; + // instantiate initial storyboard view controller MYCustomViewController *viewController = [MYCustomStoryboard instantiateInitialViewController]; -// getting segue identifier -NSString *segueID = [self <#segueID#>SegueIdentifier]; +// getting a segue identifier +NSString *segueID = [self <#segueID#>Identifier]; -// getting cell/view identifier (for UITableView or UICollectionView) +// getting a cell/view identifier (for UITableView or UICollectionView) NSString *cellID = [self <#cellID#>Identifier]; // dequeue collection view reusable cell -MYCustomCell *cell = [self dequeue<#cellID#>CellForIndexPath:indexPath ofCollectionView:collectionView]; +MYCustomCell *cell = [self dequeue<#cellID#>ForIndexPath:indexPath ofCollectionView:collectionView]; // dequeue collectiov view reusable view -MYCustomView *view = [self dequeue<#viewID#>ViewForIndexPath:indexPath ofKind:kind ofCollectionView:collectionView]; +MYCustomView *view = [self dequeue<#viewID#>ForIndexPath:indexPath ofKind:kind ofCollectionView:collectionView]; ``` **Note:** in the above examples, `self` refers to a subclass of UIViewController. +### Generated names + +Here is a list of the generated names along with naming recommendations. + +- **Storyboard:** "Foo_iPhone.storyboard" -> *MYFoo_iPhone*Storyboard +- **Scene:** "Foo Controller" -> instantiate*FooController*, *fooController*Identifier +- ... or "Foo Scene" -> instantiate*FooScene*, *fooScene*Identifier +- **Segue:** "Foo Segue" -> perform*FooSegue*, *fooSegue*Identifier +- **Reusable Cell/View:** "Foo Cell" -> dequeue*FooCell*ForIndexPath, *fooCell*Identifier +- **Constraint:** "fooConstraint" -> *fooConstraint*OriginalConstant + + ## Command-line options (common to all three tools) Usage: diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index 014848f..91ad45a 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -57,6 +57,9 @@ @interface CGUMethod : NSObject +/// A string that will be added right before the declaration of the method in the .h file with '///' prepended to each line. +@property NSString *documentation; + /// Specifies if this is a class method rather than an instance method. @property BOOL classMethod; diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 3050992..4d2c904 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -325,7 +325,15 @@ - (NSString *)name; - (NSString *)interfaceCode; { - return [NSString stringWithFormat:@"%@ (%@)%@;", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments]; + NSMutableString *interfaceCode = [NSMutableString string]; + if (self.documentation) { + NSArray *lines = [self.documentation componentsSeparatedByString:@"\n"]; + for (NSString *line in lines) { + [interfaceCode appendFormat:@"/// %@\n", line]; + } + } + [interfaceCode appendFormat:@"%@ (%@)%@;", (self.classMethod ? @"+" : @"-"), self.returnType ?: @"void", self.nameAndArguments]; + return [interfaceCode copy]; } - (NSString *)implementationCode; diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index d97fcc5..8b5a93a 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -33,22 +33,6 @@ - (NSString *)IDS_camelcaseString; return output; } -- (NSString *)IDS_stringWithSuffix:(NSString *)suffix; -{ - if (![self hasSuffix:suffix]) { - return [self stringByAppendingString:suffix]; - } - return self; -} - -- (NSString *)IDS_asPrefixOf:(NSString *)suffix; -{ - if (![suffix hasPrefix:self]) { - return [self stringByAppendingString:suffix]; - } - return self; -} - @end @@ -177,8 +161,9 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; return [storyboardIdentifier1 caseInsensitiveCompare:storyboardIdentifier2]; }]; + // generate the MYMainStoryboard class CGUClass *storyboardClass = [CGUClass new]; - storyboardClass.name = [self.classPrefix IDS_asPrefixOf:[NSString stringWithFormat:@"%@Storyboard", storyboardName]]; + storyboardClass.name = [NSString stringWithFormat:@"%@%@Storyboard", self.classPrefix, storyboardName]; storyboardClass.superClassName = @"NSObject"; self.classes[storyboardClass.name] = storyboardClass; @@ -221,11 +206,20 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; if (storyboardIdentifier) { [identifiers removeObject:storyboardIdentifier]; // prevent user from using the old string, they can now access it via [MYMainStoryboard instantiate...] + // output + [MYMainStoryboard myCustomViewControllerIdentifier] + CGUMethod *customViewControllerIdentifierMethod = [CGUMethod new]; + customViewControllerIdentifierMethod.classMethod = YES; + customViewControllerIdentifierMethod.returnType = @"NSString *"; + customViewControllerIdentifierMethod.nameAndArguments = [NSString stringWithFormat:@"%@Identifier", [storyboardIdentifier IDS_camelcaseString]]; + customViewControllerIdentifierMethod.body = [NSString stringWithFormat:@" return @\"%@\";", storyboardIdentifier]; + [storyboardClass.methods addObject:customViewControllerIdentifierMethod]; + // output + [MYMainStoryboard instantiateMyCustomViewController] CGUMethod *instantiateCustomViewControllerMethod = [CGUMethod new]; + instantiateCustomViewControllerMethod.documentation = [NSString stringWithFormat:@"@return The scene with id '%@' from the '%@' storyboard.", storyboardIdentifier, storyboardName]; instantiateCustomViewControllerMethod.classMethod = YES; instantiateCustomViewControllerMethod.returnType = [NSString stringWithFormat:@"%@ *", className]; - instantiateCustomViewControllerMethod.nameAndArguments = [@"instantiate" stringByAppendingString:[[storyboardIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Controller"]]; + instantiateCustomViewControllerMethod.nameAndArguments = [NSString stringWithFormat:@"instantiate%@", [storyboardIdentifier IDS_titlecaseString]]; instantiateCustomViewControllerMethod.body = [NSString stringWithFormat:@" return [[self storyboard] instantiateViewControllerWithIdentifier:@\"%@\"];", storyboardIdentifier]; [storyboardClass.methods addObject:instantiateCustomViewControllerMethod]; } @@ -246,13 +240,13 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; // output - [(MYCustomViewController *) myCustomSegueIdentifier] CGUMethod *segueIdentifierMethod = [CGUMethod new]; segueIdentifierMethod.returnType = @"NSString *"; - segueIdentifierMethod.nameAndArguments = [[[segueIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Segue"] stringByAppendingString:@"Identifier"]; + segueIdentifierMethod.nameAndArguments = [NSString stringWithFormat:@"%@Identifier", [segueIdentifier IDS_camelcaseString]]; segueIdentifierMethod.body = [NSString stringWithFormat:@" return @\"%@\";", segueIdentifier]; [viewControllerClassCategory.methods addObject:segueIdentifierMethod]; // output - [(MYCustomViewController *) performMyCustomSegue] CGUMethod *performSegueMethod = [CGUMethod new]; - performSegueMethod.nameAndArguments = [@"perform" stringByAppendingString:[[segueIdentifier IDS_titlecaseString] IDS_stringWithSuffix:@"Segue"]]; + performSegueMethod.nameAndArguments = [NSString stringWithFormat:@"perform%@", [segueIdentifier IDS_titlecaseString]]; performSegueMethod.body = [NSString stringWithFormat:@" [self performSegueWithIdentifier:[self %@] sender:nil];", segueIdentifierMethod.nameAndArguments]; [viewControllerClassCategory.methods addObject:performSegueMethod]; } @@ -265,24 +259,20 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; // output - (NSString *)[(MYCustomViewController *) myCustomCellIdentifier]; CGUMethod *reuseIdentifierMethod = [CGUMethod new]; reuseIdentifierMethod.returnType = @"NSString *"; - reuseIdentifierMethod.nameAndArguments = [[reuseIdentifier IDS_camelcaseString] IDS_stringWithSuffix:@"Identifier"]; + reuseIdentifierMethod.nameAndArguments = [NSString stringWithFormat:@"%@Identifier", [reuseIdentifier IDS_camelcaseString]]; reuseIdentifierMethod.body = [NSString stringWithFormat:@" return @\"%@\";", reuseIdentifier]; [viewControllerClassCategory.methods addObject:reuseIdentifierMethod]; NSString *elementName = reuseIdentifierElement.name; // E.g. collectionViewCell, tableViewCell, etc. NSString *additionalMethodArguments = nil; - NSString *suffix = nil; NSString *code = nil; if ([elementName isEqualToString:@"tableViewCell"]) { - suffix = @"Cell"; additionalMethodArguments = @"ofTableView:(UITableView *)tableView"; code = [NSString stringWithFormat:@"[tableView dequeueReusableCellWithIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; } else if ([elementName isEqualToString:@"collectionViewCell"]) { - suffix = @"Cell"; additionalMethodArguments = @"ofCollectionView:(UICollectionView *)collectionView"; code = [NSString stringWithFormat:@"[collectionView dequeueReusableCellWithReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; } else if ([elementName isEqualToString:@"collectionReusableView"]) { - suffix = @"View"; additionalMethodArguments = @"ofKind:(NSString *)kind ofCollectionView:(UICollectionView *)collectionView"; code = [NSString stringWithFormat:@"[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@\"%@\" forIndexPath:indexPath]", reuseIdentifier]; } else { @@ -295,7 +285,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; // output - (MYCustomCell *)[(MYCustomViewController *) dequeueMyCustomCellForIndexPath:ofTableView:] CGUMethod *dequeueMethod = [CGUMethod new]; dequeueMethod.returnType = [NSString stringWithFormat:@"%@ *", reuseIdentifierClassName]; - dequeueMethod.nameAndArguments = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [[reuseIdentifier IDS_titlecaseString] IDS_stringWithSuffix:suffix], additionalMethodArguments]; + dequeueMethod.nameAndArguments = [NSString stringWithFormat:@"dequeue%@ForIndexPath:(NSIndexPath *)indexPath %@", [reuseIdentifier IDS_titlecaseString], additionalMethodArguments]; dequeueMethod.body = [NSString stringWithFormat:@" return %@;", code]; [viewControllerClassCategory.methods addObject:dequeueMethod]; } @@ -313,7 +303,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; // ouptut - (CGFloat)[(MYCustomViewController *) myCustomConstraintOriginalConstant] CGUMethod *constraintMethod = [CGUMethod new]; constraintMethod.returnType = @"CGFloat"; - constraintMethod.nameAndArguments = [NSString stringWithFormat:@"%@OriginalConstant", [[propertyName IDS_camelcaseString] IDS_stringWithSuffix:@"Constraint"]]; + constraintMethod.nameAndArguments = [NSString stringWithFormat:@"%@OriginalConstant", [propertyName IDS_camelcaseString]]; constraintMethod.body = [NSString stringWithFormat:@" return %f;", constant]; [viewControllerClassCategory.methods addObject:constraintMethod]; } From 79a0a60d8e137e7f8fcaf50e0b2729ebc89323b9 Mon Sep 17 00:00:00 2001 From: Michael Thole Date: Wed, 16 Apr 2014 11:01:19 -0700 Subject: [PATCH 11/13] Expect NSCalibratedRGBColorSpace from color lists, as its what +[UIColor colorWithRed:green:blue:alpha:] uses. --- colordump/CDColorListDumper.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colordump/CDColorListDumper.m b/colordump/CDColorListDumper.m index d77d4cb..c59ccaa 100644 --- a/colordump/CDColorListDumper.m +++ b/colordump/CDColorListDumper.m @@ -33,8 +33,8 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; for (NSString *key in colorList.allKeys) { NSColor *color = [colorList colorWithKey:key]; - if (![color.colorSpaceName isEqualToString:NSDeviceRGBColorSpace]) { - printf("Color %s isn't device RGB. Skipping.", [key UTF8String]); + if (![color.colorSpaceName isEqualToString:NSCalibratedRGBColorSpace]) { + printf("Color %s isn't generic calibrated RGB. Skipping.", [key UTF8String]); continue; } From e78ed899b0c40ba6a928beef9c554a629c67d54a Mon Sep 17 00:00:00 2001 From: OY Date: Thu, 15 May 2014 15:29:17 -0700 Subject: [PATCH 12/13] =?UTF-8?q?Improved=20methodNameForKey=20to=20handle?= =?UTF-8?q?=20illegal=20characters=20in=20the=20name.=20Added=20view=20con?= =?UTF-8?q?trollers=20and=20segues=20with=20illegal=20identifiers=20to=20s?= =?UTF-8?q?how=20that=20it=20handles=20them=20correctly=20(i.e.=20it=20no?= =?UTF-8?q?=20longer=20causes=20build=20errors).=20Created=20a=20test=20co?= =?UTF-8?q?lor=20scene=20which=20shows=20that=20Michael=20Thole=E2=80=99s?= =?UTF-8?q?=20color=20fix=20is=20correct.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base.lproj/MoreExamples.storyboard | 152 ++++++++++++++++++ CodeGenTestApp/CGTAMasterViewController.m | 14 ++ CodeGenTestApp/Test App.clr | Bin 101 -> 196 bytes Shared/CGUCodeGenTool.h | 3 +- Shared/CGUCodeGenTool.m | 79 +++++++-- assetgen/AGCatalogParser.m | 2 +- colordump/CDColorListDumper.m | 2 +- identifierconstants/IDStoryboardDumper.m | 11 +- 8 files changed, 241 insertions(+), 22 deletions(-) diff --git a/CodeGenTestApp/Base.lproj/MoreExamples.storyboard b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard index 35ecc47..18fdbe2 100644 --- a/CodeGenTestApp/Base.lproj/MoreExamples.storyboard +++ b/CodeGenTestApp/Base.lproj/MoreExamples.storyboard @@ -699,6 +699,62 @@ Xcode will generate a warning, but everything should still compile. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1216,6 +1272,101 @@ Xcode will generate a warning, but everything should still compile. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1223,6 +1374,7 @@ Xcode will generate a warning, but everything should still compile. + diff --git a/CodeGenTestApp/CGTAMasterViewController.m b/CodeGenTestApp/CGTAMasterViewController.m index 7ae03f1..af75f3c 100644 --- a/CodeGenTestApp/CGTAMasterViewController.m +++ b/CodeGenTestApp/CGTAMasterViewController.m @@ -12,6 +12,8 @@ #import "CGTAImagesCatalog+RuntimeHackery.h" #import "CGTAMainStoryboardIdentifiers.h" #import "CGTAFlagCollectionViewCell.h" +#import "CGTATestAppColorList.h" +#import "CGTAMoreExamplesStoryboardIdentifiers.h" @interface CGTAMasterViewController () @@ -24,6 +26,18 @@ @interface CGTAMasterViewController () @implementation CGTAMasterViewController +//#define ColorTester +#ifdef ColorTester +- (void)viewDidLoad +{ + [super viewDidLoad]; + UIViewController *controller = [CGTAMoreExamplesStoryboard instantiateColorTestScene]; + UIView *runTimeView = [controller.view viewWithTag:1]; + runTimeView.backgroundColor = [CGTATestAppColorList blueGenericRGBColor]; + [self.navigationController pushViewController:controller animated:NO]; +} +#endif + #pragma mark - NSObject - (void)awakeFromNib; diff --git a/CodeGenTestApp/Test App.clr b/CodeGenTestApp/Test App.clr index cb9c10346e2d2db0a3d27acd82682f42b022dfb9..6c7256f49b9b4ee8a5ea79498094bda40eef86f6 100644 GIT binary patch delta 127 zcmYd2!Z<;ak$Iw&f+%APOBxU~=UDErZ5G{aWZ!JtAw1DrDtb!G%@T6TDNR+-a8J!kEy_$*2y%DQYy+ugf~sct4^;gZrdkl9+9kCt JGdUHa7y!8aG&}$R delta 31 lcmX@Ym^wj{k#VAwf*?~1OBxU`H1FGMYu~(diONKODFBpm3OE1& diff --git a/Shared/CGUCodeGenTool.h b/Shared/CGUCodeGenTool.h index 91ad45a..1017e7d 100644 --- a/Shared/CGUCodeGenTool.h +++ b/Shared/CGUCodeGenTool.h @@ -34,7 +34,8 @@ - (void)writeOutputFiles; -- (NSString *)methodNameForKey:(NSString *)key; +/// @param camelCase If this is true, it will use @e camelCase, otherwise it will use @e TitleCase. ++ (NSString *)identifierNameForKey:(NSString *)key camelCase:(BOOL)camelCase; @end diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 4d2c904..43bdd49 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -199,19 +199,78 @@ - (void)writeOutputFiles; NSLog(@"Wrote %@ to %@", self.className, currentDirectory); } -- (NSString *)methodNameForKey:(NSString *)key; ++ (NSString *)identifierNameForKey:(NSString *)key camelCase:(BOOL)camelCase; { - NSMutableString *mutableKey = [key mutableCopy]; - // If the string is already all caps, it's an abbrevation. Lowercase the whole thing. - // Otherwise, camelcase it by lowercasing the first character. - if ([mutableKey isEqualToString:[mutableKey uppercaseString]]) { - mutableKey = [[mutableKey lowercaseString] mutableCopy]; + /* + Standard examples (camelCase): + My Scene Identifier -> mySceneIdentifier + my scene identifier -> mySceneIdentifier + + Abbreviation examples (camelCase only feature) (i.e. uppercase first word -> lowercase): + USA -> usa + usa -> usa + USA2 -> usa2 // considered uppercased first word + USoA -> uSoA + usa image -> usaImage + USA image -> usaImage + image USA -> imageUSA // abbreviations are only searched for in the first word + + Number handling examples (camelCase): + 2url -> _2url // identifiers cannot begin with a number + A2test -> A2test // A2 is in uppercase, so it assumes it is a namespace + 2Atest -> _2Atest + 22test -> _22test + + Special character handling (camelCase): + usa image -> usaImage // space acts as word separator + usa-image -> usaImage // any non alphanumeric character acts as a word separator + usa_image -> usa_image // underscores are preserved + + More examples (camelCase): + NSString -> nSString + NS String -> nsString + my url -> myUrl + my uRL -> myURL + my URL -> myURL + my u r l -> myURL + myUrl list -> myUrlList + myUrl MyUrl -> myUrlMyUrl + myUrl NSUrl -> myUrlNSUrl + myURL NSUrl -> myURLNSUrl + */ + + NSRegularExpression *wordsRegex = [NSRegularExpression regularExpressionWithPattern:@"\\w+" options:0 error:NULL]; + NSArray *wordMatches = [wordsRegex matchesInString:key options:0 range:NSMakeRange(0, key.length)]; + NSMutableArray *words = [NSMutableArray array]; + for (NSTextCheckingResult *wordMatch in wordMatches) { + [words addObject:[key substringWithRange:wordMatch.range]]; + } + NSAssert([words count] > 0, @"Must have at least one character in an identifier."); + + // Process the first word. + if (camelCase) { + // If the first word is all caps, it's an abbrevation. Lowercase it. + // Otherwise, camelcase it by lowercasing the first character. + if ([words[0] isEqualToString:[words[0] uppercaseString]]) { + words[0] = [words[0] lowercaseString]; + } else { + words[0] = [words[0] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[words[0] substringToIndex:1] lowercaseString]]; + } } else { - [mutableKey replaceCharactersInRange:NSMakeRange(0, 1) withString:[[key substringToIndex:1] lowercaseString]]; + words[0] = [words[0] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[words[0] substringToIndex:1] uppercaseString]]; } - [mutableKey replaceOccurrencesOfString:@" " withString:@"" options:0 range:NSMakeRange(0, mutableKey.length)]; - [mutableKey replaceOccurrencesOfString:@"~" withString:@"" options:0 range:NSMakeRange(0, mutableKey.length)]; - return [mutableKey copy]; + + // If the first word starts with a number, prefix with underscore. + if ([words[0] rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == 0) { + words[0] = [NSString stringWithFormat:@"_%@", words[0]]; + } + + // Process the remaining words (uppercase first letter of each word). + for (NSInteger i = 1; i < [words count]; i++) { + words[i] = [words[i] stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[words[i] substringToIndex:1] uppercaseString]]; + } + + return [words componentsJoinedByString:@""]; } @end diff --git a/assetgen/AGCatalogParser.m b/assetgen/AGCatalogParser.m index ef8a100..a47e3ac 100644 --- a/assetgen/AGCatalogParser.m +++ b/assetgen/AGCatalogParser.m @@ -65,7 +65,7 @@ - (void)findImageSetURLs; - (void)parseImageSetAtURL:(NSURL *)url; { NSString *imageSetName = [[url lastPathComponent] stringByDeletingPathExtension]; - NSString *methodName = [self methodNameForKey:imageSetName]; + NSString *methodName = [CGUCodeGenTool identifierNameForKey:imageSetName camelCase:YES]; NSURL *contentsURL = [url URLByAppendingPathComponent:@"Contents.json"]; NSData *contentsData = [NSData dataWithContentsOfURL:contentsURL options:NSDataReadingMappedIfSafe error:NULL]; if (!contentsData) { diff --git a/colordump/CDColorListDumper.m b/colordump/CDColorListDumper.m index c59ccaa..cbf97ac 100644 --- a/colordump/CDColorListDumper.m +++ b/colordump/CDColorListDumper.m @@ -41,7 +41,7 @@ - (void)startWithCompletionHandler:(dispatch_block_t)completionBlock; CGFloat r, g, b, a; [color getRed:&r green:&g blue:&b alpha:&a]; - NSString *declaration = [NSString stringWithFormat:@"+ (UIColor *)%@Color;\n", [self methodNameForKey:key]]; + NSString *declaration = [NSString stringWithFormat:@"+ (UIColor *)%@Color;\n", [CGUCodeGenTool identifierNameForKey:key camelCase:YES]]; [self.interfaceContents addObject:declaration]; NSMutableString *method = [declaration mutableCopy]; diff --git a/identifierconstants/IDStoryboardDumper.m b/identifierconstants/IDStoryboardDumper.m index 8b5a93a..ca95bdb 100644 --- a/identifierconstants/IDStoryboardDumper.m +++ b/identifierconstants/IDStoryboardDumper.m @@ -18,19 +18,12 @@ @implementation NSString (IDStoryboardAddition) - (NSString *)IDS_titlecaseString; { - NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSMutableString *output = [NSMutableString string]; - for (NSString *word in words) { - [output appendFormat:@"%@%@", [[word substringToIndex:1] uppercaseString], [word substringFromIndex:1]]; - } - return output; + return [CGUCodeGenTool identifierNameForKey:self camelCase:NO]; } - (NSString *)IDS_camelcaseString; { - NSString *output = [self IDS_titlecaseString]; - output = [NSString stringWithFormat:@"%@%@", [[output substringToIndex:1] lowercaseString], [output substringFromIndex:1]]; - return output; + return [CGUCodeGenTool identifierNameForKey:self camelCase:YES]; } @end From 5b7c86cda79827520488a2c5e741521628947ee8 Mon Sep 17 00:00:00 2001 From: OY Date: Fri, 16 May 2014 11:53:17 -0700 Subject: [PATCH 13/13] Removed references to class extension, since none of the functionality of making a class extension was in place. --- Shared/CGUCodeGenTool.m | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Shared/CGUCodeGenTool.m b/Shared/CGUCodeGenTool.m index 43bdd49..f9d1b76 100644 --- a/Shared/CGUCodeGenTool.m +++ b/Shared/CGUCodeGenTool.m @@ -13,8 +13,8 @@ typedef NS_ENUM(NSInteger, CGUClassType) { CGUClassType_Definition, - CGUClassType_Extension, CGUClassType_Category + // TODO: add extension in the future? }; @interface CGUCodeGenTool () @@ -26,9 +26,8 @@ @interface CGUCodeGenTool () @interface CGUClass () /// The class type is determined by the following: -/// - If there is a superClassName, this is a class definition -/// - If there is a clategoryName, this is a category -/// - Otherwise, this is a class extension +/// - If there is a categoryName, this is a category. +/// - Otherwise this is a class definition. @property (readonly) CGUClassType classType; @end @@ -317,7 +316,7 @@ - (NSString *)interfaceCode; NSMutableString *result = [NSMutableString string]; if (self.classType == CGUClassType_Definition) { - [result appendFormat:@"@interface %@ : %@\n", self.name, self.superClassName]; + [result appendFormat:@"@interface %@ : %@\n", self.name, self.superClassName ?: @"NSObject"]; } else { [result appendFormat:@"@interface %@ (%@)\n", self.name, self.categoryName]; } @@ -354,10 +353,10 @@ - (NSString *)implementationCode; - (CGUClassType)classType; { - if (self.superClassName) { - return CGUClassType_Definition; + if (self.categoryName.length > 0) { + return CGUClassType_Category; } else { - return self.categoryName.length == 0 ? CGUClassType_Extension : CGUClassType_Category; + return CGUClassType_Definition; } }