Kjell Kjell - 6 months ago 43
Objective-C Question

NSString writeToFile losing Indentation

I have an OSX application that creates Objective-C code. In other words, writes to two files, .h and .m.

To write to a file, I am using NSString writeToFile atomically true, encoding NSUTF8StringEncoding.

Although my two files are created with the text correctly, all formatting is lost. Everything ends up aligned to the left, even when imported into Xcode. Although I know I can select the text and indent it using ctrl + i, this is not the solution I want.

Now, when I copy the NSString to the clipboard, and paste it into Xcode, the formatting stays how it should.

Does anyone know a way where I can keep the text indentation the way it was in Xcode without having to write rules about how many curly braces are open? Here is some modified example code of what I am doing so far:

NSString *code = @"for (int i = 0; i < 10; i++) {\n";
code = [code stringByAppendingString:@"NSLog(@\"HelloWorld\");\n"];
code = [code stringByAppendingString:@"}\n"];

// If I run the two lines below, then my NSString is copied to the clipboard. When I paste into Xcode, formatting stays as it should.
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:code forType:NSStringPboardType];

// If I run the code below instead, then two files are created. But they do not keep the formatting.
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setNameFieldStringValue:@"MyClass"];
[panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSError *errorH = nil;
NSError *errorM = nil;

NSString *pathH = [[[panel URL] path] stringByAppendingString:@".h"];
NSString *pathM = [[[panel URL] path] stringByAppendingString:@".m"];

[code writeToFile:pathH atomically:true encoding:NSUTF8StringEncoding error:&errorH];
[code writeToFile:pathM atomically:true encoding:NSUTF8StringEncoding error:&errorM];

if (!errorH && !errorM) {
NSLog(@"Success");
} else {
NSLog(@"Error Saving Files");
}
}
}];


EDIT: After not getting a clear way to do this elegantly, I simply had to write some of my own formatting code to mimic the formatting done by Xcode. It doesn't work 100% properly... for instance, when I am adding multiple items to an array using:

@[@"string1",
@"string2",
@"string3"];\n


This doesn't stay properly indented to where the array began. And I'm sure there are other cases that my code doesn't handle. Not a huge deal though. If a solution comes to mind later, I can implement it. But for now, the formatting simply won't be as pretty as intended. Here is a method, and a helper method, that anyone can use to implement similar functionality (handles tabs and curly braces only)

- (NSString *) formatWithIdentationForExport : (NSString *) theString {
NSString *s = @"";

NSMutableArray *fileLines = [[NSMutableArray alloc] initWithArray:[theString componentsSeparatedByString:@"\n"] copyItems: YES];
int numberOfCurlyBraces = 0;

for (int i = 0; i < fileLines.count; i++) {
NSString *currentLine = fileLines[i];
int numberOfOpenBracesInLine = [self getNumberOfOccurancesOf:@"{" inString:currentLine];
int numberOfCloseBracesInLine = [self getNumberOfOccurancesOf:@"}" inString:currentLine];
numberOfCurlyBraces -= numberOfCloseBracesInLine;

for (int j = 0; j < numberOfCurlyBraces; j++) {
currentLine = [NSString stringWithFormat:@"\t%@", currentLine];
}
currentLine = [currentLine stringByAppendingString:@"\n"];
s = [s stringByAppendingString:currentLine];

numberOfCurlyBraces += numberOfOpenBracesInLine;
}

return s;
}

- (int) getNumberOfOccurancesOf : (NSString *) substring inString : (NSString *) str {
int count = 0, length = (int)[str length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound) {
range = [str rangeOfString:substring options:0 range:range];
if(range.location != NSNotFound) {
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
return count;
}

Answer

Use "\t" for an indent. I won't do it for every line of your code, but do something like

[[NSMutableString alloc] initWithString:@"for (int i = 0; i < 10; i++) {\n"];
[codeStr appendString:@"\t"];[codeStr appendString:@"NSLog(@\"center\");"];