Save Plain Text to File (Obj-C) - objective-c

My application (for Mac) generates some HTML... This HTML then needs to be saved to a .html file. I'm trying to use a NSSavePanel like this:
- (IBAction)saveFile:(id)sender{
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel setRequiredFileType:#"html"];
[savePanel setTitle:#"Save Code to File"];
if ([savePanel runModal] == NSOKButton)
{
[[_codeStore RTFFromRange:
NSMakeRange(0, [[_codeStore string] length])]
writeToURL:[savePanel URL] atomically:YES];
NSLog(#"saved");
}
}
My problem is that this does not save is a plain text. For example, when I open the generated file in a web browser, the html shows up, but
{\rtf1\ansi\ansicpg1252\cocoartf1157\cocoasubrtf700
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural
\f0\fs24 \cf0
is at the top of the page...

The problem looks to be
[[_codeStore RTFFromRange: NSMakeRange(0, [[_codeStore string] length])]
writeToURL: [savePanel URL]
atomically: YES];
You're outputting an RTF document that contains the HTML. You need
[[_codeStore string] writeToURL: [savePanel URL]
atomically: YES
encoding: NSUTF8StringEncoding
error: nil];
This will take the raw NSString from your NSAttributedString _codeStore and write that to a file.

i think using rtf encoding is causing the problem since it can write beyond 8 bit using escape sequence. You may try to use simple nsstring way of writing to file.
BOOL ok = [string writeToFile:path atomically:YES
encoding:NSUnicodeStringEncoding error:&error];

Related

Writing attributed strings line-by-line to an RTF file

I'm trying to write a series of attributed strings to an RTF file, line-by-line at various points in my application sit is running (you can think of this as log data, only with attributes). The file is created just fine and it would appear that all the data is being written to the file, but when I open the RTF file only the first line written to the file appears. I suspect that there's something written to the file in the first write that essentially end the RTF file, but I'm not quite sure what that is.
- (void) writeLineWithSizeAndStyle: (NSString *) line : (CGFloat)fontSize : (NSFontTraitMask) traits {
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:line];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
NSFontManager *fm = [NSFontManager sharedFontManager];
NSRange range = [line rangeOfString:line];
NSFont *font = [NSFont systemFontOfSize:fontSize];
font = [fm convertFont:font toHaveTrait:traits];
[attrString addAttribute: NSFontAttributeName value:font range:range];
NSData *rtfData = [attrString RTFFromRange:NSMakeRange(0, attrString.length) documentAttributes:#{}];
// Go the the end of the file, write the data, and close the file
[fileHandle seekToEndOfFile];
[fileHandle writeData: rtfData];
[fileHandle closeFile];
}
Is there something I need to to in converting each attributed string to NSData that effectively tells RFT not to terminate the file upon writing?
Thanks!
Update:
Here's the code for solution 2 I proposed in the comments section. This operates on an instance variable in the class object (playByPlay) that simply appends each new line onto the entire "log". This works great, but again is only really a solution for sufficiently small files.
- (void) writeLineWithSizeAndStyle: (NSString *) line : (CGFloat)fontSize : (NSFontTraitMask) traits {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:line];
NSRange range = [line rangeOfString:line];
NSFontManager *fm = [NSFontManager sharedFontManager];
NSFont *font = [NSFont systemFontOfSize:fontSize];
font = [fm convertFont:font toHaveTrait:traits];
[attrString addAttribute: NSFontAttributeName value:font range:range];
[playByPlayText appendAttributedString:attrString];
NSData *rtfData = [playByPlayText RTFFromRange:NSMakeRange(0, playByPlayText.length) documentAttributes:#{}];
[fileHandle writeData: rtfData];
[fileHandle closeFile];
}

NSString writeToFile, NSSavePanel and write permissions

I've only been leaning Cocoa/Objective C for a few days so apologies that this is probably simple/obvious but it's got me stumped.
I've written this handler for saving 3 floats to a text file. However when I'm running it the files are not being saved. Could anyone suggest if there's an error in my code or if you think there's something else (like file write permissions) preventing the file from being written.
Research has lead me to look into Sandboxing, but that gets confusing very quickly and I'm hoping just running the app from xcode in debug would let me write to my user directory.
Heres the code:
- (IBAction)saveResultsAction:(id)sender {
//Sets up the data to save
NSString *saveLens = [NSString stringWithFormat:#"Screen width is %.02f \n Screen Height is %.02f \n Lens is %.02f:1",
self.myLens.screenWidth,
self.myLens.screenHeight,
self.myLens.lensRatio];
NSSavePanel *save = [NSSavePanel savePanel];
long int result = [save runModal];
if (result == NSOKButton) {
NSURL *selectedFile = [save URL];
NSLog(#"Save URL is %#", selectedFile);
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.txt", selectedFile];
NSLog(#"Appended URL is %#", fileName);
[saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
}
a NSURL object is no POSIX path..
its a URL and getting its description doesnt make it a path
NSString *fileName = [selectedFile.path stringByAppendingPathExtension:#"txt"];
BUT as said, you shouldnt have to append the .txt at all. just use what the panel returns. Else, there would be sandboxd errors because you dont have access rights to the modified filename :)
NSString *fileName = selectedFile.path;
The problem is that you don't need to append the file extension to the URL.The extension is already there.You could directly do this:
if (result == NSOKButton)
{
[saveLens writeToURL: [save URL]
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
I see you've already accepted an answer, but it may also be helpful to know how to debug this type of issue using NSError pointers.
Cocoa uses NSError with method calls which generate error conditions, which richly encapsulate errors. (Objective-C also has exceptions, but they're reserved for cases of programmer error, like an array index out of bounds, or a nil parameter that should never be.)
When you have a method which accepts an error pointer, usually it also return a BOOL indicating overall success or failure. Here's how to get more information:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
NSLog(#"error: %#", error);
}
Or even:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
[NSApp presentError:error];
}

Appending to the end of a file with NSMutableString

I have a log file that I'm trying to append data to the end of. I have an NSMutableString textToWrite variable, and I am doing the following:
[textToWrite writeToFile:filepath atomically:YES
encoding: NSUnicodeStringEncoding error:&err];
However, when I do this all the text inside the file is replaced with the text in textToWrite. How can I instead append to the end of the file? (Or even better, how can I append to the end of the file on a new line?)
I guess you could do a couple of things:
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:aPath];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[textToWrite dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
Note that this will append NSData to your file -- NOT an NSString. Note that if you use NSFileHandle, you must make sure that the file exists before hand. fileHandleForWritingAtPath will return nil if no file exists at the path. See the NSFileHandle class reference.
Or you could do:
NSString *contents = [NSString stringWithContentsOfFile:filepath];
contents = [contents stringByAppendingString:textToWrite];
[contents writeToFile:filepath atomically:YES encoding: NSUnicodeStringEncoding error:&err];
I believe the first approach would be the most efficient, since the second approach involves reading the contents of the file into an NSString before writing the new contents to the file. But, if you do not want your file to contain NSData and prefer to keep it text, the second option will be more suitable for you.
[Update]
Since stringWithContentsOfFile is deprecated you can modify second approach:
NSError* error = nil;
NSString* contents = [NSString stringWithContentsOfFile:filepath
encoding:NSUTF8StringEncoding
error:&error];
if(error) { // If error object was instantiated, handle it.
NSLog(#"ERROR while loading from file: %#", error);
// …
}
[contents writeToFile:filepath atomically:YES
encoding:NSUnicodeStringEncoding
error:&err];
See question on stackoverflow
Initially I thought that using the FileHandler method in the accepted answer that I was going to get a bunch of hex data values written to my file, but I got readable text which is all I need. So based off the accepted answer, this is what I came up with:
-(void) writeToLogFile:(NSString*)content{
content = [NSString stringWithFormat:#"%#\n",content];
//get the documents directory:
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString *fileName = [documentsDirectory stringByAppendingPathComponent:#"hydraLog.txt"];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:fileName];
if (fileHandle){
[fileHandle seekToEndOfFile];
[fileHandle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
else{
[content writeToFile:fileName
atomically:NO
encoding:NSUTF8StringEncoding
error:nil];
}
}
This way if the file doesn't yet exist, you create it. If it already exists then you only append to it. Also, if you go into the plist and add a key under the information property list UIFileSharingEnabled and set the value to true then the user can sync with their computer and see the log file through iTunes.
And here is a (slightly adopted) Swift version of Chase Roberts' solution:
static func writeToFile(content: String, fileName: String = "log.txt") {
let contentWithNewLine = content+"\n"
let filePath = NSHomeDirectory() + "/Documents/" + fileName
let fileHandle = NSFileHandle(forWritingAtPath: filePath)
if (fileHandle != nil) {
fileHandle?.seekToEndOfFile()
fileHandle?.writeData(contentWithNewLine.dataUsingEncoding(NSUTF8StringEncoding)!)
}
else {
do {
try contentWithNewLine.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding)
} catch {
print("Error while creating \(filePath)")
}
}
}

Parsing of m3u files in Objective-C for iPhone from file system or URL

The example below should take a link from m3u playlist and add it to anArray. (So I will get the NSArray(NSMutableArray) with certain links in it)
NSString *fileContents = [NSString stringWithContentsOfFile:#"myfile.m3u" encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines = [fileContents componentsSeparatedByString:#"\n"];
NSLog (#"%#",lines);
All the time I had (null) in NSLog Message.
All the time when I try NSLog or if/else statement to check is there is link in array it gives me the null object in it.
After that I thought the problem was in m3u type and I've tried to change type in txt and read. (For those who don't know, M3U is just the text in UTF-8 encoding and the changing type should give the result)
Then I've tried the .txt files but it doesn't work too. So there is the code of it.
//Check if there is my file
NSString *addPath = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"m3u" ];
if ([fileMgr fileExistsAtPath:addPath] ) {
NSLog(#"Yes.We see the file");
}
else {
NSLog(#"Nope there is no file");
}
//Rename
NSString *path1 = addPath;
NSString *theNewFilename = [path1 stringByReplacingOccurrencesOfString:#"m3u" withString:#"txt"];
NSLog(#"Renamed file adress is %#", theNewFilename);
//Check if there is our renamed file(file manager was allocated before)
NSString *addPath1 = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"txt" ];
if ([fileMgr fileExistsAtPath:addPath1] ) {
NSLog(#"Yes we had the renamed file");
}
else {
NSLog(#"No we don't");
}
Checking is there is m3u file worked fine. I had Addres to Renamed file too. But when it was checking is there is renamed file, there was no file (null in NSLog).
After all that stuff, and without any hope to reach my destination I've tried to read txt file line by line separated by /n with 5 links in it.
NSString *fileContents1 = [NSString stringWithContentsOfFile:#"myfile.txt" encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines1 = [fileContents1 componentsSeparatedByString:#"\n"];
NSLog (#"%#",fileContents1);
NSLog (#"%#",lines1);
Both Messages were NULL
One more thing all this stuff I tried to make in -(IBAction)fileRead { } linked to button
(Yes I've presed button every time to check my NSLog)Program was checked in iPhone Simulator. Will be glad if someone say what is the trouble. Also if there is easier way to make this with url. (Tried Couple times with NSUrl and had Nothing but null )
Just because you've changed the path doesn't mean that you've renamed/moved/copied an item, path is just a string. Use NSFileManager methods like
– moveItemAtURL:toURL:error: or
– moveItemAtPath:toPath:error:.
Also, NSString doesn't care about extension, so it's completely safe to read your m3u file to NSString, no need to rename it.
NSString *addPath = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"m3u" ];
if ([fileMgr fileExistsAtPath:addPath] ) {
NSLog(#"Yes.We see the file");
NSString *fileContents1 = [NSString stringWithContentsOfFile:addPath encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines1 = [fileContents1 componentsSeparatedByString:#"\n"];
NSLog (#"%#",fileContents1);
NSLog (#"%#",lines1);
}
else {
NSLog(#"Nope there is no file");
}

Plist won't write to file

When i try this code:
if ([[NSFileManager defaultManager] fileExistsAtPath:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] == NO) {
NSMutableDictionary* tempDict = [[NSMutableDictionary alloc]initWithObjectsAndKeys:
[[NSString alloc] initWithString:#"0"],#"completedGames",
[[NSString alloc] initWithString:#"0"],#"floatsCollected",
[[NSString alloc] initWithString:#"0"],#"sec",
[[NSString alloc] initWithString:#"0"],#"subScore",
[[NSString alloc] initWithString:#"0"],#"highScore",
[[NSString alloc] initWithString:#"0"],#"longestSec", nil];
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
}
and this outputs with
2012-04-14 19:15:10.009 Staying Afloat[3227:9c07] written file
so the loop has run, but the file isn't written?
can anyone point my in the right dirrection for saving plists to non-localized places?
EDIT: this is for mac
You need to expand your path using -(NSString *)stringByExpandingTildeInPath when you write and check if the file exist.
Edit: Read the method writeToFile:automatically in the NSDictionary documentation. It says
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
So just do something like
[tempDict writeToFile:[[NSString stringWithString:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] stringByExpandingTildeInPath] atomically:YES];
first you can't just write :
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
because you don't check if the file was really written with success or not.
you should write :
if([tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES]) {
NSLog(#"written file");
} else {
NSLog(#"file not written");
}
WriteToFile method returns a Boolean.
I always use NSUserDefaults which takes care of everything for you with things like user prefs or storing app states between running. Off the top of my head I'd suggest you haven't got the right privileges to write a plist and what happens if you accidentally destroy a vital key and value for the os? Have a look at
Best practices for handling user preferences in an iPhone MVC app?
Are you sure -writeToFile:atomically: returns YES?