Writing an NSString to a Relative File - objective-c

I'm getting strange behavior writing NSString and NSData objects to relative file paths. Here's an example:
NSString *string = #"I am a file!";
NSError *error = nil;
NSString *fileName = #"text.txt";
BOOL written = [string writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (written) {
NSLog(#"Successfully written to file.");
} else {
NSLog(#"Error: %#", [error localizedDescription]);
}
When I run this I always get "Successfully written to file.", but the file is never there. Somehow the program thinks it was successful and no error is generated.
What am I doing wrong? (I'm on Mac OS X Lion)

This writes to the current directory. The default current directory when you run something under Xcode 4 is going to be ~/Library/Developer/Xcode/DerivedData/<prodDir>/Build/Products/<configuration>. You can override this using a Scheme. When you run a program from the commandline, then the current directory is whatever the current directory was when you ran the program. If you use Finder to launch the app, then the current directory will often be /.
In principle, it's fine to write the current working directory. It's very common to do this in command-line apps. So regarding #craig's comment about writeToFile: expecting an absolute path, I don't think that's really true. It expects and writes to a path. It doesn't care if it's absolute or relative. This is a Foundation class, and is just as useful in a command-line program as a GUI.
But in a GUI app, you should avoid relative paths. In principle, you could set the current directory and then write the file, but this is usually a bad idea in a large program since it's not thread safe (there is only one cwd for the whole program). And GUI apps tend to have somewhat unpredictable current directories, so it doesn't make for a good user experience.
But to the question of why you didn't get an error, it's because it probably successfully wrote it. You just didn't know where to look.

NSFileManager * fm = [NSFileManager new];
NSString * dirPath = [fm currentDirectoryPath];
NSString * absPath = [dirPath stringByAppendingPathComponent:#"myfile.file"];
[fm release];
keep in mind that currentDirectoryPath reflects your programs working directory until you change it with -changeCurrentDirectoryPath:, the programs working directory can be different depending on how it was launched, and can't be relied upon.

The first parameter to the writeToFile: method (in your example) is a relative path, but you probably want to use an absolute path. Otherwise, the system will place your files relative to the current executable. When you're running inside Xcode, this might not be where you expect them to end up. (As Rob mentioned in his answer, this location is somewhat buried, and can change depending on which version of Xcode you're using.)
If you want to build up a directory path using NSString objects, I would recommend the stringByAppendingPathComponent: method:
...
NSString *directory = #"/Users/Mikael/Desktop";
NSString *filename = #"MyFile.txt";
NSString *fullPath = [directory stringByAppendingPathComponent:filename];
...
Note that this method will take care of making sure your slashes are well-formed.

Related

Cannot get the correct data out of a file using stringWithContentsOfFile

I haven been working on this problem for a couple of hours but I still could not get it worked. I have tried all the solutions I found on stackoverflow but nothing worked. The following is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSFileManager *fileManager;
NSString *currentPath;
NSString *content;
content = [[NSString alloc] init];
NSString *filename;
filename = #"/Tasty Noodle House.xlsx";
fileManager = [NSFileManager defaultManager];
currentPath = [fileManager currentDirectoryPath];
NSError* error;
content = [NSString stringWithContentsOfFile:[currentPath stringByAppendingString:filename] encoding:NSUTF16LittleEndianStringEncoding error:&error];
NSLog(#"current path: %#",currentPath);
NSLog(#"filecontent: %#",content);
NSLog( #"error: %#", error );
}
I have checked that the path is correct and the file did exist in that path(currentPath). However, the problem is that the line NSLog(#"filecontent: %#",content); is not printed out at all. The printout looks like the following:
2016-01-23 11:30:11.367 ScaryBugsMac[1377:222946] current path: /Users/username/Library/Developer/Xcode/DerivedData/ScaryBugsMac-buobqwjccigdqthjejrzmgasnzyf/Build/Products/Debug
2016-01-23 11:30:11.367 ScaryBugsMac[1377:222946] error: (null)
Based on my experience, it should print out at least "filecontent: (null)".But it is really weird nothing get printed out.
I have also tried using the enconding: NSUTF8StringEncoding, it gave me the error:
2016-01-23 11:32:44.708 ScaryBugsMac[1404:230157] current path: /Users/username/Library/Developer/Xcode/DerivedData/ScaryBugsMac-buobqwjccigdqthjejrzmgasnzyf/Build/Products/Debug
2016-01-23 11:32:44.708 ScaryBugsMac[1404:230157] filecontent: (null)
2016-01-23 11:32:44.709 ScaryBugsMac[1404:230157] error: Error Domain=NSCocoaErrorDomain Code=261 "The file “Tasty Noodle House.xlsx” couldn’t be opened using text encoding Unicode (UTF-8)." UserInfo=0x60000007a740 {NSFilePath=/Users/username/Library/Developer/Xcode/DerivedData/ScaryBugsMac-buobqwjccigdqthjejrzmgasnzyf/Build/Products/Debug/Tasty Noodle House.xlsx, NSStringEncoding=4}
Two other things:
I have tried all the encodings and the problem falls into one of the two cases above.
I could open the xlsx file using Excel on my Mac without any problem. So I don't think there is anything wrong with the file.
I would really appreciate any help, thank you!
More likely than not, the file contents are either compressed (IIRC, the XLSX definition allows the contents to be straight-compressed).
Or there are characters in the file that are, effectively, causing it to be considered corrupt.
Loading it into an NSString probably isn't that useful. If it is compressed, you'll need to decompress it first. If it truly is an XLSX file, then you'll want to use, at least, an XML reader to read the contents (and, better yet, some likely already available open source library that can read said file's contents).
Excel .xlsx files contain binary data. A .xlsx file is similar to a package bundle.
It does not contain plain text, therefore it cannot be encoded to NSString

SQLite will not prepare query when accessing database in Xcode

I'm a beginner iPhone developer trying to take information out of a sqlite database in Xcode 4.3. I have my database (which is named DB_Info.sqlite) in the same directory as my .h and .m files, and I also dragged the database into the folders section on the left bar in Xcode.
Could you please take a quick look at my code and let me know where my mistake is? I have used NSLogs to identify where the problem occurs, at the very last if statement, and it's written in comments. Thank you so much in advance!
#import <sqlite3.h>
#implementation Player
{
sqlite3 *DB_Info;
NSString *databasePath;
NSString *docsDir;
NSArray *dirPaths;
}
-(Player*)createPlayer:(NSString*)playerName
{
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"DB_Info.sqlite"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &DB_Info) == SQLITE_OK) { //works fine
NSString *querySQL = [NSString stringWithFormat: #"SELECT * FROM playerlist WHERE fullName=\"%#\"", playerName];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(DB_Info, query_stmt, -1, &statement, NULL) == SQLITE_OK) { //PROBLEM: This is where the problem is, and the if statement never goes through
//....rest of code here
} else {
NSLog(#"Error");
}
}
First, rather than just saying "Error", log the SQL error message
NSLog(#"%s SQL error '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(database), sqlite3_errcode(database));`
It will tell you precisely what's going wrong. A common error on people's first time SQL projects is that the table is not found. If so, read on. (If not, feel free to ignore the rest of this.)
Second, you're looking for your database in your Documents folder. Did you explicitly copy it from your bundle to your Documents folder at some point? Or did you create it programmatically? But if you prepared it in advance, it won't be in the Documents folder until you copy it there.
Third, I'd also suggest that you consider using sqlite3_open_v2 instead of sqlite3_open. You are checking to see if that's SQLITE_OK, but that may be giving a false sense of security. The sqlite3_open will create a database if it's not there, which is clearly not your intent. Your app should presumably being copying the db from the bundle or creating the database and the tables for you before you get to this method (and you're not showing that, so I'm not sure if you're doing that). Anyway, the sqlite3_open_v2 function will not create the database if it's not there unless you explicitly request it does so by including SQLITE_OPEN_CREATE. So, something like the following will not create the database if it's not found:
if (sqlite3_open_v2(dbpath, &DB_Info, SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK) {
On the off chance that a blank database has been created for you, I'd suggest you reset your simulator via "Reset Content and Settings" on the simulator's menu, or explicitly delete the app, so any blank databases that might have been created can be removed. If you're running this on a device, delete the app and reinstall it.
Fourth, if the database has been created in advance, have you confirmed that the database has been included in the "Copy Bundle Resources" setting? E.g.:
Fifth, if you're running the app on the simulator, you can always browse the simulator's file structure and make sure your files are where you think they are. You can also run the Mac OS sqlite program to inspect the database that the simulator is using to make sure everything is ok. You can even test your SQL right in the db that the simulator uses, which can be useful for diagnostic purposes. (Or, if you don't like the Mac text based sqlite program, you can also buy/download graphical tools, such as Base.) Before you can do this, you might first want to configure your Mac so you can easily browse the Simulator's files requires that you fire up the Terminal program, and issue the command:
chflags nohidden ~/Library
Then you can browse to "~/Library/Application\ Support/iPhone\ Simulator/5.1/Applications/" and then browse the various apps that you have and make sure you db file is there.

Listing localized files

Konichiwa folks,
I'm banging my head over a non-standard procedure to read a bunch of localized xml located in my project.
My purpose is to read recursively all these files in order to feed a coredata sqlite db to create various lang-based versions of the same DB.
In the first place, I've tried an old school technique, like:
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath];
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bundleRoot error:nil];
NSArray *onlyXMLs = [dirContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"self ENDSWITH '.xml'"]];
for (NSString *tString in onlyXMLs) {
/* stuff */
}
with no luck at all, because it pops out all the non-localized xmls in my project and nothing more.
so I was wondering if there would be a way to get those damn't localized xmls out there.
thanks in advance.
If u got doubts or questions don't be a stranger, drop me a line.
-k-
ok, solved it!
for the ones who are seeking for an answer to the problem, here's the solution:
NSArray *onlyXMLs = [[NSBundle mainBundle] pathsForResourcesOfType:#"xml" inDirectory:nil forLocalization:#"English"];
remember that the parameter that carries the desired language
must always be the same of the .plist directory containing the desired files in your project.
so if you've got a English.plist with the stuff you need in it, that must also be the name string to pass (like shown in the above example), otherwise if you're dealing with a en.plist, #"en" shall be the string.
that's it, that's all.

NSDocumentDirectory or NSBundle resourcePath with Sqlite?

i was wondering why we search a path with NSDocumentDirectory at first here :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"Sports.sqlite"];
and later on we compare this path with another path, using this time resourcePath from the NSBundle :
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Sports.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath error:&error];
What is the difference between both?
In this situation you are (normally one time only, unless you need to restore database) copying the database from your read only bundle into your documents directory so that a user can read/write to it. This is useful if you want to pre-seed a database or just have the structure set up.
Your documents directory is read/write and your bundle is not therefore you need to have the sqlite in your documents directory for it to be used properly.
The first part of code is simply getting you the path for where you want the sqlite file to live in your documents directory. Which ends up being held in writableDBPath.
Next you get the path from your bundle (defaultDBPath) and use the two paths to
... copyItemAtPath:defaultDBPath toPath:writableDBPath ...
This gives you a read/write database that you provide in your bundle. Why would you do this instead of running your SQL on the device to create the schema? This allows you to pre-seed the database with some data. It can sometimes be easier to use a graphical tool to set up and edit your sqlite file
What you mean by "comparing" the two paths?
What I see is a file copy from your app resources directory to the user document directory.
So what is happening is that a default version of Sports.sqlite that is to be bundled with the app (and thus is available in the resources directory) is copied to the user directory where the user can modify it.
The first chunk of code simply build the destination path (a string); the second chunk build the source path (a string) and then makes the copy.
Does it make sense?

Get image paths from NSBundle in Objective C?

I have about 60 images I want to store in Core Data, 30 of which are avatars and have the prefix of avt_filename_00X.png and 30 of them are smaller and have a different prefix.
Rather than storing all the images as BLOBs in Core Data/SQLite, I want to store the paths for each image found (in the same way you would store image paths for a MySQL database).
However I am not sure how to grab the path of the image as found in NSBundle.
I can get the path to the NSDocumentDirectory via:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager fileExistsAtPath:documentsDirectory];
NSLog(#"documentsDirectory = %#", documentsDirectory);
And I can load the image and add it to an array.
if (qty == 0)
{
//NSLog(#"fileToLoad = %#", fileToLoad);
UIImage *img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileToLoad ofType:fileExt]];
[self.avtList addObject:img];
[img release];
} else {
// load multiple image into an array
// not coded yet
}
But, what I'm unsure of is:
How do I grab the path where the computer found the image once its inside the NSBundle?
How can I be sure that the path will work when the app is on a device?
The idea would be to get all the images stored in an array and then push them to Core Data/SQLite at a later time.
The correct way to get the full path to a resource in the main bundle is:
[[NSBundle mainBundle] pathForResource:#"avt_filename_00X" ofType:#"png"]
(or you can supply the empty string for 'ofType' if you prefer to include the extension in the resource name)
But nowhere in the docs is the path guaranteed to remain the same across devices, operating system iterations, etc. It's the path to that file from the application bundle in the current environment, guaranteed to remain valid for the duration of this run of the application only.
Because the path to the application, and hence to its resources, isn't guaranteed to stay the same, I think it's explicitly unsafe to put it in an SQL database by any means.
Could you perhaps adopt a scheme whereby a filename starting in / is a complete path, one without a / at the start is assumed to be in the bundle, meaning that you can apply the logic on the outside of the database?
How can I be sure that the path will
work when the app is on a device?
Therein lies the rub: you can't. You would be best to let the paths be handled on-the-fly, and perhaps just store the file names instead.