Objective C Exception during file reading? - objective-c

I am trying to read a text file line by line, and the text file will be a really small file so I just used:
NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
However, an exception is raised on that line, saying:
[NSURL getFileSystemRepresentation:maxLength:]: unrecognized selector sent to instance 0x7f92c40e1890
I'm really new to Objective-C and I don't get why this is happening...
Thanks in advance.
NSString *filePath;
NSOpenPanel *fileBrowser = [NSOpenPanel openPanel];
[fileBrowser setCanChooseFiles:YES];
[fileBrowser setCanChooseDirectories:YES];
if ([fileBrowser runModal] == NSOKButton) {
NSArray *files = [fileBrowser URLs];
for ( int i = 0; i < [files count]; i++ ) {
filePath = [files objectAtIndex:i];
}
}
Is this because of the [fileBrowser URLs] part?
Thank you.

It looks like filePath is an NSURL, but stringWithContentsOfFile:encoding:error: expects the path as an NSString.
Try this:
NSString *fileContents = [NSString stringWithContentsOfURL:filePath encoding:NSUTF8StringEncoding error:nil];

You are getting this error because NSURL does not have a method getFileSystemRespresentation, this is in NSString.
You can either use this method on your fileContents string, or pass your fileContents string to NSURL's URLWithString method.

Related

How do I handle file names with spaces?

I am using this code below to copy a file selected in the file browser and copying it to the temp directory with a different name. But when I select a file with spaces in it, the program throws an error saying it cannot find the specified fine path. I have tried using escape methods but they do not work either. Are there any other ways to handle file names with spaces?
Code starts here:
[openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
[openPanel close];
if (result == NSFileHandlingPanelOKButton) {
myString = [self randomStringWithLength:7];
NSString *filePath = [[[openPanel URLs] objectAtIndex:0] absoluteString];
NSLog(#"%#", filePath);
NSString *strTemp = [self extractString:filePath toLookFor:#"//" skipForwardX:2 toStopBefore:#".png"];
NSLog(#"%#",strTemp);
NSString *realThing = [strTemp stringByReplacingOccurrencesOfString:#"%20" withString:#"\\ "];
//strTemp = [strTemp stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#", realThing);
NSString* fullPath = [NSString stringWithFormat:#"/tmp/%#.png", myString];
NSLog(fullPath);
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:realThing toPath:fullPath error:&error];
if(error) {
NSLog(#"Error!!!");
NSLog(#" error => %# ",error);
}
else {
NSLog(#"Saved to temp directory");
}
Anyone have experience with this? Thanks
Your conversion of the URL to a path is much too complicated and error-prone.
Just use the path method:
NSString *filePath = [[[openPanel URLs] objectAtIndex:0] path];
Alternatively, use copyItemAtURL:... instead of copyItemAtPath:....
You also should check the return value of copyItemAtPath:... as the indicator
of a failure:
if (![[NSFileManager defaultManager] copyItemAtPath:filePath toPath:fullPath error:&error]) {
NSLog(#" error => %# ",error);
}
Compare Handling Error Objects Returned From Methods:
Important: Success or failure is indicated by the return value of the
method. Although Cocoa methods that indirectly return error objects in
the Cocoa error domain are guaranteed to return such objects if the
method indicates failure by directly returning nil or NO, you should
always check that the return value is nil or NO before attempting to
do anything with the NSError object.
You seem to be trying to convert URLs to file paths by hand. Use fileSystemRepresentation instead.

Cannot append to NSString

I'm trying to append the file extension to a the stringValue returned by a subclassed NSTextFieldCell
I've tried everything I knew and could find on the internet, but this is just giving me a headache
the method is the following:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSMutableString *filename = [[NSMutableString alloc] init];
[filename appendString:self.stringValue];
NSString *iconFileName = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:filename] stringByAppendingPathExtension:#"png"];
NSLog(#"%#", iconFileName);
}
The returned value is without the extension though!
I've also tried the following:
filename = [NSString stringWithFormat: #"%#.png", filename];
This returns the "filename" string without the ".png"
Similarly:
filename = [filename stringByAppendingString: #".png"];
returns just the "filename"
The table column where this cell belongs to is bound to an NSObject, and the method that sends the data to the column is the following:
- (NSString *) nationString {
NSMutableString *string = [[NSMutableString alloc] init];
int index = 0;
if (nationAddress && nationAddress > 0x0) {
index = [[[[controller database] nationsAddressIndex] valueForKey:[NSString stringWithFormat:#"%lli", nationAddress]] intValue];
Nation *nationality = [[[controller database] nations] objectAtIndex:index];
[string appendString:[nationality name]];
}
else {
[string appendString:#"---"];
}
return string;
}
Anyone has any idea why this might be happening, or can suggest any alternatives?
Any help will be appreciated
Thanks
This should return the complete path with extension:
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Default.png"];
NSLog(#"%#", path);
So, assuming self.stringValue includes the extension, your method should work with this:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSString *iconFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.stringValue];
NSLog(#"%#", iconFileName);
}
If it doesn't include the extension, try this:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSString *strWithPath = [NSString stringWithFormat:#"%#.png", self.stringValue];
NSString *iconFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:strWithPath];
NSLog(#"%#", iconFileName);
}
Just for test. Try to use this code and update here a output values:
NSMutableString *filename = [[NSMutableString alloc] init];
[filename appendString:self.stringValue];
NSLog(#"text1: %# ;", filename);
filename = [NSString stringWithFormat: #"%#.png", filename];
filename = [NSString stringWithFormat: #"%#.png%#", filename, filename];
NSLog(#"text2: %# ;", filename);
These should work (barring a typo):
NSString* filename = #"abc";
NSString* result1 = [NSString stringWithFormat:#"%#.png", filename];
NSString* result2 = [filename stringByAppendingString:#".png"];
NSMutableString* result3 = [filename mutableCopy];
[result3 appendString:#".png"];
If they don't appear to be working then you have some problem with how you're initializing or displaying your values.
Hint: Place an NSLog(#"The answer is %#", resultN); statement immediately after each of the above (with "resultN" changed appropriately) to see what you're getting. Keep in mind that if you look from a different object you may be looking at different variables.

OSX directory with spaces

I'm having an issue with opening directories that have spaces in them. My code looks like this:
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseDirectories:YES];
if ( [openDlg runModal] == NSOKButton )
{
NSArray* files = [openDlg URLs];
NSString* directoryName = [[files objectAtIndex:0] absoluteString];
directoryURL = [files objectAtIndex:0];
NSLog(#"Directory Name: %#", directoryName);
NSArray *directoryArray = [directoryName componentsSeparatedByString:#"/"];
NSString* currentDirectory = [directoryArray objectAtIndex:(directoryArray.count- 2)];
[directoryBox setTitle:currentDirectory];
}
When I select a directory name with spaces the files are not displayed in a table and the output in the NSLog looks like this:
Directory Name:
file://localhost/Users/Rich/Software%20Bisque/
Any ideas?
The -URLs method of of NSOpenPanel returns instances of NSURL, not file system paths. While NSURLs have become the preferred way to refer to files, you can easily change to a file system path by using NSURL's -path method.
Note that there are many methods specific to working with file system paths that are added to NSString in NSPathUtilities.h. You could probably rewrite your code to incorporate those (double-check that I've got your targeted directory okay):
NSArray* files = [openDlg URLs];
NSString* directoryName = [[files objectAtIndex:0] path];
directoryURL = [files objectAtIndex:0];
NSLog(#"Directory Name: %#", directoryName);
// NSArray *directoryArray = [directoryName pathComponents];
// NSString* currentDirectory = [directoryArray objectAtIndex:(directoryArray.count- 2)];
NSString *currentDirectory = [[directoryName stringByDeletingLastPathComponent]
lastPathComponent];
[directoryBox setTitle:currentDirectory];
You could try removing the percent escapes in the directoryName string - I don't think the system needs them there. Something like:
directoryName = [directoryName stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSOpenPanel not correctly accessing NSUrl from filename with Whitespace

Did a search, didn't find any issues on this.
My application takes .csv files, switches the data around and spits it out in a way my company finds useful. The issue is that the program seems to require filenames without whitespace. For instance "My data.csv" will not work, but "My_data.csv" will work. Here is what I believe to be the relevant code
NSArray *allowedTypes = [NSArray arrayWithObjects:#"CSV",#"csv",nil];
openDLG = [NSOpenPanel openPanel];
[openDLG setDelegate:self];
[openDLG setCanChooseFiles:YES];
[openDLG setAllowedFileTypes:allowedTypes];
NSInteger openReturn = [openDLG runModal];
if (openReturn == NSFileHandlingPanelOKButton) {
NSArray *rawCSVs = [openDLG URLs];
NSEnumerator *enumerator = [rawCSVs objectEnumerator];
id object;
while ((object = [enumerator nextObject])) {
NSString *tempDump = [NSString stringWithContentsOfURL:object encoding:NSUTF8StringEncoding error:NULL];
NSArray *bigArray = [tempDump csvRows];
int total = [bigArray count];
....do other things
In debug mode, the "object" comes up as nil for filenames with whitespace and "tempdump" string comes up as #"nil" for filenames with whitespace, but comes up wonderfully for names_with_underscores
An URL does not permit every character, so you will need to do some conversion.
NSString *rawString = [NSString stringWithString:#"/Users/myusername/test 1.txt"];
NSString *escapedString = [rawString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:escapedString];
NSLog(#"url: %#", url);

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)")
}
}
}