Failure to write NSString to file on iPad - objective-c

I'm using a text file to save the changes made by a user on a list (the reason that I'm doing this is so that I can upload the text file to a PC later on, and from there insert it into an Excel spreadsheet). I have 3 data structures: A NSMutableArray of keys, and a NSMutableDictionary who's key values are MSMutableArrays of NSStrings.
I iterate through these data structures and compile a file string that looks much like this:
(Key);(value)\t(value)\t(value):\n(Key);(value).. .so on.
SO, onto the actual question: When I attempt to save it, it fails. I'm 99% sure this is because of the file path that I'm using, but I wanted backup to check this out. Code follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *filePath = [paths objectAtIndex:0];
NSString *fileString = [NSString stringWithString:[self toFileString]];
if(![fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:NULL]){
NSLog(#"File save failed");
} else {
// do stuff
}
(Code above is re-copied, since the actual code is on a different computer. It compiles, so ignore spelling errors?)
I tried using NSError, but I got bogged down in documentation and figured I might as well ask SO while trying to figure out how to properly use NSError (might be a little bit of an idiot, sorry).
99% sure it's the NSArray *paths line that's tripping it up, but I don't know how else to get the documents directory.
Edit: Problem solved, and one final question: If I save it to the App's document directory, where can I go after I close the app to see if it saved properly? If it works like I think it does, isn't it sandboxed in with the app's installation on the simulator? (i.e. no way of checking it)

NSLog() that filePath string. I think you're trying to write to the directory itself, not to a file.
Try this instead:
filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"myfile.txt"];

What is the file name you want to save? The method
NSArray *paths = NSSearchPathForDirectoriesInDomains(...);
NSString *filePath = [paths objectAtIndex:0];
...
if(![fileString writeToFile:filePath ...
means you are saving the string into a file path which has the same name as a folder. This will of course fail. Please give it a name, e.g.
NSString* fileName = [filePath stringByAppendingPathComponent:#"file.txt"];
if(![fileString writeToFile:fileName ...
and try again.
BTW, to use NSError:
NSError* theError = nil;
if(![fileString writeToFile:fileName ... error:&theError]) {
// ^^^^^^^^^
NSLog(#"Failed with reason %#", theError);
// theError is autoreleased.
}

Related

Issues playing local files with AVPlayer?

Basically, I'm writing a video file to the Application Support directory and then saving it's path:
NSString *guid = [[NSUUID new] UUIDString];
NSString *outputFile = [NSString stringWithFormat:#"video_%#.mp4", guid];
NSString *outputDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempPath = [outputDirectory stringByAppendingPathComponent:outputFile];
NSURL *fileURL = [NSURL fileURLWithPath:tempPath]
// code to write a video to fileURL
I save the string path itself by calling [fileURL path];
Now later when I try to create an AVAssetItem from it, I can't actually get it to play.
EDIT:
Ok, so it seems the issue is the space in Application Support. I tried saving the file to just the Library directory and it worked fine.
So the question becomes, how can I play a video I save in the Application Support directory. I wasn't able to create a valid NSURL without escaping the space (when I tried it would return a nil NSURL) but it seems that the space/escaping doesn't allow it to play correctly.
Assume the NSURL to NSString conversion is required (unless it really should be avoided).
Also, side note, but if anyone could give some insight as to why this question was down voted so I can improve the quality of my questions, that would be appreciated. I don't understand?
While I am not informed enough to opine about whether this would change if I had used matt's recommended methods: URLForDirectory:inDomain:appropriateForURL:create:error: and URLByAppendingPathComponent: the actual issue here isn't converting an NSURL to NSString
It's that I'm saving the full URL ([fileURL path]). This is because the [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; dynamically changes and the full path won't always point me to the appropriate file.
Instead I need to save just the name of my file (in my case I needed to persist outputFile) and then later dynamically build the the full path when I need it.
The same exact process:
NSString *outputDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *tempPath = [outputDirectory stringByAppendingPathComponent:outputFile];
NSURL *fileURL = [NSURL fileURLWithPath:tempPath];
worked just fine.
TLDR: Saving a full path doesn't work

Why my program can run in Xcode, but cannot running as a separate app?

My program loads some data from a file and then draws them.
The file-reading part is like this:
- (void)load_file
{
NSFileHandle *inFile = [NSFileHandle fileHandleForReadingAtPath:#"map_data"];
NSData *myData=[inFile readDataToEndOfFile];
NSString *myText=[[NSString alloc]initWithData:myData encoding:NSASCIIStringEncoding];
NSArray *values = [myText componentsSeparatedByString:#"\n"];
for (NSString *string in values) {
NSArray *lines=[string componentsSeparatedByString:#" "];
if ([lines count] != 2) break;
NSPoint point= NSMakePoint([lines[0] floatValue], [lines[1] floatValue]);
[points addObject:[NSValue valueWithPoint:point]];
}
[self setNeedsDisplay:YES];
}
When debugging, I put the data file in the directory of [NSBundle mainBundle], and the program works fine.
However, when I use achieve to take the app out, it never runs. I put the data file in the same path with the app, but it seems fail to load it.
Update
I tried to use c++, but still fails.
- (void)load_file
{
ifstream inf("map_data");
double x, y;
while (inf >> x >> y) [points addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
inf.close();
}
I tried to change the build scheme to release and run, which is fine. But whenever I go directly into the finder of app and double click it, it does not work and seems nothing is loaded.
add the file to the project as a Resource (this will cause it to be copied into the app wrapper in the right spot)
use `[[NSBundle mainBundle] pathForResource:#"map_data" ofType:nil];
That should give you the path to the file. The file should not be manually copied, it should not be next to the app wrapper, nor should you [conjecture] ever try changing or replacing the file once it is in your app wrapper.
The reason why it seems to work sometimes is mere coincidence. You are passing a partial path to NSFileHandle and it happens that the current working directory of your app sometimes points to the right spot such that the data file is available.
I'm not sure how relative paths are handled by NSFileHandle, but usually you set up paths using the NSBundle class.
NSString *path = [[NSBundle mainBundle] pathForResource:#"myfile" ofType:#"ext"];
You can also simply initialize an NSString from the contents of a file, you don't need to first read it into an NSData using NSFileHandle.
NSString *text = [[NSString alloc] initWithContentsOfFile:path
encoding:NSASCIIStringEncoding error:nil];
(Use the error parameter, if you want proper error handling)

writeToFile doesn't change the content of the file xcode4 objective-c

I finished writing a little program, which is able to read and write a/into a .txt file.
When I execute the program, everything is running fine except that the content of the file doesn't change permanently. I got a writeToFile and "readFile" button and the content seems to change every time I press one of them, but when I open the file manually (while testing or after shutting down the program) theres still the origin content in it.
Doesn't the "real" file content change while just using the simulator? Or is it just me making some bad mistakes?
-(IBAction)buttonPressed { //The writeToFile Method
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"txt"];
NSString *writeData = enterText.text;
NSError *error;
BOOL ok = [writeData writeToFile:filePath atomically:NO encoding:NSUnicodeStringEncoding error:&error];
if (!ok)
{
NSLog(#"Error while writing file at %#/n%#",filePath,[error localizedFailureReason]);
}
testText.text =#"File saved!";
enterText.text = #"";
enterText.placeholder =#"Enter your text here";
}
testText = TextView for Output
enterText = TextField for Input
Your filePath variable is pointing to a file within the resource bundle of your app (which is not writable). What you need to do is locate the user's Documents folder, and create your file there.

File write with [NSBundle mainBundle] fails

I am trying to take content from one file and write it into another. I am reading fine, but I am not able to write it into another file.
I have a database of words. I want to separate the words into different files based on the number of letters. All four letter words go into one file, and so on. I added a txt file called "4letter" into my resources and the following is my code:
NSError *error;
//READ
NSString *dbFile = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"txt"];
NSString *test = [NSString stringWithContentsOfFile:dbFile encoding:NSUTF8StringEncoding error:&error];
//convert from string to array
NSArray *lines = [test componentsSeparatedByString:#"\n"];
NSFileHandle *logFile = nil;
logFile = [NSFileHandle fileHandleForWritingAtPath:[[NSBundle mainBundle] pathForResource:#"4letter" ofType:#"txt"]];
//Test if write works
for (int i=0; i<5; i++)
{
NSString *randomAnagram = [[lines objectAtIndex:i] lowercaseString];
[logFile writeData: [randomAnagram dataUsingEncoding: NSNEXTSTEPStringEncoding]];
}
In iOS, you can't write into a file in your app's bundle -- the entire bundle is read-only. Use a path into the Documents folder instead.
See special File System Programming Guide for better understnading.
In iOS, you can't write into a file in your app's bundle -- the entire bundle is read-only.
Consider reading iOS Data Storage Guidelines to better understand the purpose of directories below, in context of iCloud backup.
<Application_Home>/AppName.app
This is the bundle directory containing the app itself. Do not write
anything to this directory. To prevent tampering, the bundle directory
is signed at installation time. Writing to this directory changes the
signature and prevents your app from launching again.
<Application_Home>/Documents/
Use this directory to store critical user documents and app data
files. Critical data is any data that cannot be recreated by your app,
such as user-generated content. The contents of this directory can be
made available to the user through file sharing. The contents of this
directory are backed up by iTunes.
<Application_Home>/Library/
This directory is the top-level directory for files that are not user
data files. You typically put files in one of several standard
subdirectories but you can also create custom subdirectories for files
you want backed up but not exposed to the user. You should not use
this directory for user data files. The contents of this directory
(with the exception of the Caches subdirectory) are backed up by
iTunes. For additional information about the Library directory, see
“The Library Directory Stores App-Specific Files.”
See full list (tmp/, Documents/Inbox) in iOS Standard Directories: Where Files Reside
UPDATE
I use NSFileManager method URLForDirectory:inDomain:appropriateForURL:create:error:
Like Caleb said, you can't write to your app's directory, but you can write to your app's Documents folder. You can get it like this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
Your app's bundle is read-only. There is two ways I could see:
1) Write in documents folder:
NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path =  [myPathList  objectAtIndex:0];
2) Use sqlite database. This is the same as 1 (you must save db in documents anyway), but you're using sqlite database. I think this is better than a lot of txt and plist files: here's a tutorial on the topic.
I use the following code :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:#"set.txt"];
NSString *data=#"Kostas";
[data writeToFile:appFile atomically:YES];
NSString *myData = [NSString stringWithContentsOfFile:appFile];
NSLog(#"Data : %# ",myData);

sqlite db creation issue

Im teaching myself objective-c and currently trying to work out how to integrate a database into my application. Ive looked at many example, forums and tutorial but none have worked, what am i doing wrong?
Most of the examples come ready with a [projectName].sqlite db in the application. How does that get added? Not seen one explanation of how that ends up in the project.
I've created my Object Model and classes. Im running a method which checks if DB exists, if not, it creates the db. Ive found several examples where different paths are used and im not sure which is correct, Some examples copied part of my project files into the documents/mainDatabase.sqlite folder! Is the following correct, if so why am i getting an error?
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to create writable database file with message 'The operation couldn’t be completed. No such file or directory'.
Using these causes the above error, and yes the defaultDBPath does exist
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents
defaultDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/Documents/mainDatabase.sqlite
writableDBPath:
/Users/myuser/Library/Application Support/iPhone Simulator/4.2/Applications/DFBAD921-CEBF-471E-B98B-04FDF2620146/dbProj12.app/mainDatabase.sqlit
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence - we don't want to wipe out a user's DB
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentDirectory = [self applicationDocumentsDirectory];
NSLog(#"documentDirectory = %#", documentDirectory);
NSString *writableDBPath = [documentDirectory stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", writableDBPath);
BOOL dbexits = [fileManager fileExistsAtPath:writableDBPath];
if (!dbexits) {
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"iBountyHunter.sqlite"];
NSLog(#"defaultDBPath = %#", defaultDBPath);
NSError *error;
BOOL success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
}
Look forward to your reply.
I'm assuming you are using CoreData, but the solution should be valid for every database type.
The message stated that iBountyHunter.sqlite doesn't exists and it's a true message: that db should be added in the XCode project and packaged when you build the app.
Some apps ships with an already populated database probably modified by the developer on the mac, based on what is created in the simulator (or even in a development device).
Build and run you app on the simulator;
Look in the Documents folder on your device and copy the database;
Edit it adding custom data;
Add the db to XCode;
Add the code you've reported;
Build and let the app copy the populated database on the device.
It might be late, but i wanted to answer this question so that in future no one gets this issue.
The answer has to do with the "Target Membership".
click on the sql file in the left-pane of Xcode
Under "Target Membership", make sure the "check" is "checked" for your target build.
that's it, this solves my issue. I hope this helps you also.
Mac makes a good point, are you using sqlite or core data?
If you are using sqlite then one of the easier things to do is on application launch, copy it into your apps documents directors (unless it already exists of course, in which case do nothing)
I generally create an sqliteDB external to xcode and add it as a file (just like an image). Then in the 'applicationDidFinishLaunchingWithOptions' method (in your app delegate I call the same method, but my code is a little different. Take a look:
- (void)createEditableCopyOfDatabaseIfNeeded
{
//We always need to overwrite the DB, but first we need to extract usedQuestionIds, ObjectiveData and stats
//Once database is replaced, reinsert data
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
{
//might want to check for update and if so, back up users unique data, replace database and reinsert
}
else
{
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"yourdatabasename.sqlite"];
[fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
}
It then create another method in my app delegate that allows me to get a db connection which I can then use:
+(sqlite3 *)getNewDBConnection
{
sqlite3 *newDBconnection;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"yoursqliteDatabaseName.sqlite"];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &newDBconnection) == SQLITE_OK)
{
NSLog(#"Database opened successfully");
}
else
{
NSLog(#"Error in opening database ");
}
return newDBconnection;
}
Now wherever you are in your app you can get a connection to you db by calling:
sqlite3 *db = [YourAppDelegateName getNewDBConnection];
//do stuff with your sqlite db connection
sqlite3_close(db);
Hope that helps :)