Store and fetch documents in/from app documents folder - ios

Im writing a text editor app and im trying to store a NSString as NSData in files in the documents folder, for example i want to be able to store a file "myText.java".
My code work on the Simulator. However, on the device a file seems to be created, but when I later try to load the data from the files i get nothing.
Do i have to set some project setting to enable the documents directory on device or is my code wrong?
Here is my store code:
-(void) saveFile:(NSString*)file{
if (![file isEqual:#"Empty"]) {
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullFilePath = [documentsDirectory stringByAppendingPathComponent:file];
[[NSFileManager defaultManager] removeItemAtPath: fullFilePath error: nil];
NSLog(#"File: %#", fullFilePath);
//Above LOGS: /var/mobile/Applications/2310F459-282C-4488-AE24-D5795168F85A/Documents/fg
//save content to the documents directory
NSLog(#"Saving: %#", codeView.text);
// Logs the data i want to store
[[codeView.text dataUsingEncoding:NSASCIIStringEncoding] writeToFile:fullFilePath atomically:YES];
}
}
Here is my load file code:
-(void) loadFile:(NSString*)filename{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the documents directory:
NSString *file = [documentsDirectory stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
NSLog(#"File found");
//Yes the file is found
theDelegate.fileData = [NSData dataWithContentsOfFile:file];
NSLog(#"Data:%#",[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:file] encoding:NSASCIIStringEncoding]);
// On device and simulator data is found
[theDelegate.codeView setText:[[NSString alloc] initWithData:theDelegate.fileData encoding:NSASCIIStringEncoding]];
//codeView does not get updated with the data.
//NSLog([[NSString alloc] initWithData:theDelegate.fileData encoding:NSASCIIStringEncoding]);
[theDelegate setTitle:filename];
[theDelegate setHasOpenFile:YES];
[theDelegate.codeView setEditable:theDelegate.hasOpenFile];
[theDelegate.codeView setNeedsDisplay];
[self setLanguage:filename];
}else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"File error!" message:#"An error occured when trying to load the selected file." delegate:self cancelButtonTitle:#"OK!" otherButtonTitles:nil, nil];
[alert show];
}
}

No, you don't have to enable a setting to use the documents directory. There might or might not be an error with your code, in any case it's hard to read and not very clean (apologies for saying this.)
Try to do everything just once. I took the liberty of rewriting your code a bit, to clean it up. I then tested it with a sample app on my phone, and it works perfectly.
Code rewrite:
-(void) saveFile:(NSString*)filename{
if (![filename isEqual:#"Empty"]) {
NSString *fullFilePath = [self getFullFilePath:filename];
[[NSFileManager defaultManager] removeItemAtPath: fullFilePath error: nil];
NSLog(#"File: %#", fullFilePath);
//save content to the documents directory
NSLog(#"Saving: %#", self.codeView.text);
// Logs the data i want to store
[[self.codeView.text dataUsingEncoding:NSASCIIStringEncoding] writeToFile:fullFilePath atomically:YES];
}
}
The above code is fine, after adding the helper function.
-(void) loadFile:(NSString*)filename{
NSString *fullFilePath = [self getFullFilePath:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullFilePath]) {
NSLog(#"File found");
//Yes the file is found
NSLog(#"Data:%#",[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:fullFilePath] encoding:NSASCIIStringEncoding]);
// On device and simulator data is found
[theDelegate.codeView setText:[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:fullFilePath] encoding:NSASCIIStringEncoding]];
[theDelegate setTitle:filename];
[theDelegate setHasOpenFile:YES];
[theDelegate.codeView setEditable:theDelegate.hasOpenFile];
[theDelegate.codeView setNeedsDisplay];
//[self setLanguage:filename];
}else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"File error!" message:#"An error occured when trying to load the selected file." delegate:self cancelButtonTitle:#"OK!" otherButtonTitles:nil, nil];
[alert show];
}
}
In your old code, there might have been a problem with theDelegate.fileData = [NSData dataWithContentsOfFile:file];, if the reference is weak. Since I presume you'll always have the code in codeView, it seems unnecessary to first store it in a member variable. Also, this might lead to more bugs.
The following is a helper function, so that you don't do the exact same thing in both functions, as it might lead to bugs.
-(NSString*) getFullFilePath:(NSString*)filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullFilePath = [documentsDirectory stringByAppendingPathComponent:filename];
return fullFilePath;
}

Are you taking care that the filenames passed to loadFile: and saveFile: are exactly the same, including capitalization? Mac OS X (and by extension, the iOS simulator) uses case-insensitive filenames, while iOS is case sensitive and treats SomeFile.txt and somefile.txt as two different things.

Related

UIDocumentPickerViewController copied files disappear on download to test iPad

I am using UIDocumentPickerViewController to allow the user to select files that will be "attached" and available within the App. The concept is allow the user to send detail via email with the selected file attachments.
As each file is attached, I copy the file from the tmp Inbox (where fileManager puts the imported file) to a directory I create within the App document directory called "fileAttachments".
I list the files in a UITableView and the user can select each entry and preview the content within a QLPreviewController view using the path stored in the file object fileOJ.filePath.
It all works swimmingly well, until a reload of the project down to my test iPad, then all the files seem to disappear. My list of the files is still fine, but there is no file at the path location.
Any help with just what is happenning would be greatly appreciated.
- (IBAction)selectFilesAction:(UIBarButtonItem *)sender {
NSArray *UTIs = [NSArray arrayWithObjects:#"public.data", nil];
[self openFilePicker:UTIs];
}
- (void)openFilePicker:(NSArray *)UTIs {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:UTIs inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
documentPicker.allowsMultipleSelection = FALSE;
documentPicker.popoverPresentationController.barButtonItem = self.selectFilesButton;
[self presentViewController:documentPicker animated:TRUE completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray <NSURL *>*)urls {
NSLog(#"picked URLs %#", urls);
// selecting multiple documents is cool, but requires iOS 11
for (NSURL *documentURL in urls) {
//get file details
NSDictionary *attr = [documentURL resourceValuesForKeys:#[NSURLFileSizeKey,NSURLCreationDateKey] error:nil];
NSLog(#"object: %#", attr);
NSNumber *fileSize = [attr valueForKey:NSURLFileSizeKey];
NSDate *dateFileCreated = [attr valueForKey:NSURLCreationDateKey];
NSDateFormatter *storageDateFormat = [[NSDateFormatter alloc] init];
[storageDateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *createdDateString = [storageDateFormat stringFromDate:dateFileCreated];
MMfile *fileObj = [[MMfile alloc]init];
fileObj.fileName = documentURL.lastPathComponent;
fileObj.meetingID = _meetingID;
fileObj.fileSize = fileSize;
fileObj.fileCreateDate = createdDateString;
//move file to new directory
fileObj.filePath = [self movefile:documentURL.lastPathComponent sourceFilePath:documentURL.path directory:#"fileAttachments"];
//save file details
[self.meetingModel saveFile:fileObj];
//refresh array and reload table
self.fileArray = [self.meetingModel getFiles:self.meetingID];
[self.tableView reloadData];
}
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
NSLog(#"cancelled");
}
-(NSString *)movefile:(NSString *)filename sourceFilePath:(NSString *)sourcePath directory:(NSString *)directoryName{
// Move file from tmp Inbox to the destination directory
BOOL isDir;
NSError *error;
NSFileManager *fileManager= [NSFileManager defaultManager];
//get directory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString* directoryPath;
if (paths>0) {
NSString *documentsDirectory = [paths objectAtIndex:0];
directoryPath = [NSString stringWithFormat:#"%#/%#",documentsDirectory,directoryName];
}
if(![fileManager fileExistsAtPath:directoryPath isDirectory:&isDir])
if(![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:NO attributes:nil error:NULL])
NSLog(#"Error: Create folder failed %#", directoryPath);
NSString *destinationPath = [NSString stringWithFormat:#"%#/%#",directoryPath,filename];;
BOOL success = [fileManager moveItemAtPath:sourcePath toPath:destinationPath error:&error];
if (success) {
NSLog(#"moved file");
}else{
NSLog(#"error %#",error.description);
}
return destinationPath;
}
Found the issue. When the project is rebuilt and downloaded to the iPad the AppID changes, and as the documents path includes the AppID, so the documents path changes. Key is not to save the file path, only the file name and rebuild the path each instance. After having found the issue, I now see other similar posts I didn't find earlier. Also see Document directory path change when rebuild application

Email attachment doesn't work as file hasn't finished saving?

I have a function which saves the data extracted from a local SQLite DB to a .CSV file.
My problem is that this function also triggers another which attaches the file to an email and sends it. As the file has not yet finished saving the attachment is sent as an empty file.
Is there anyway I can check if the file has finished saving before attaching it to the email? Please see my function below:
// When the tick is visible within the animation...
// Play the bellToneSound.
AudioServicesPlaySystemSound(bellToneSound);
// Creates a temporary GPS object that we will use to save our database as a .CSV file.
GPS *saveGPS = [[GPS alloc] init];
// Finds the phone's documents directory and creates a file path in order create a new file/folder there.
NSError *error = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:#"/Jobs"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error];
}
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"/Jobs/%#.csv", proposalNumber]];
// Creates our new file, with a name matching "jobNo.csv" overrites old one if it already exists.
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
// Creates a file handler which will allow us to write to our file.
NSFileHandle *myHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
// Creates and writes the first line to our CSV file, which tells the program reading it what the column titles are.
NSString *csvTitleString =#"Source/Monitor, Latitude, Longitude";
[myHandle writeData:[csvTitleString dataUsingEncoding:NSUTF8StringEncoding]];
// Creates initializes another string object which will hold each line we want to write.
NSString *csvString = [[NSString alloc] init];
// Declares an array and fills it with all GPS objects found in our Database.
NSArray *allGPS = [[NSArray alloc]initWithArray:[database getAll]];
// While the current index value is less than the length of the array write the GPS values into our file then take a new line.
for(int i=0;i<(allGPS.count);i++){
saveGPS = [allGPS objectAtIndex:i];
csvString = [NSString stringWithFormat:#"\n %# %d, %#, %#", [saveGPS sourceMonitor], [[saveGPS positionNo] intValue], [saveGPS latitude], [saveGPS longitude]];
[myHandle seekToEndOfFile];
[myHandle writeData:[csvString dataUsingEncoding:NSUTF8StringEncoding]];
}
}
UPDATE
Here is the code you asked for.
NSString *docsDir;
NSString *realpath;
NSArray *dirPaths;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
realpath=[[NSString alloc] initWithString:[docsDir stringByAppendingPathComponent: [NSString stringWithFormat:#"/Jobs/temp.csv"]]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: realpath ] == YES)
{
[self checkIfSaved];
}
else
{
NSLog(#"file found");
// Checks if the device can send email.
if([MFMailComposeViewController canSendMail]){
// Sets the subject to data from (our current proposal number).
[mail setSubject:[NSString stringWithFormat:#"Data from %#", proposalNumber]];
[mail setMessageBody:#"Please see the attached .CSV file." isHTML:NO];
// Finds the .CSV file we just saved, and attaches it to the email.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"/Jobs/%#.csv", proposalNumber]];
NSData *attachment = [NSData dataWithContentsOfFile:[NSString stringWithFormat:#"%#", filePath]];
[mail addAttachmentData:attachment mimeType:#"text/csv" fileName:[NSString stringWithFormat:#"%#",proposalNumber]];
// Opens up the email screen.
[self presentViewController:mail animated:YES completion:NULL];
}
else
{
// Tells the user that there device cannot send email.
NSLog(#"This device cannot send email");
// Creates a popup window to inform the user that their location wasn't updates.
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Error"
message:#"Unable to send email. Have you set up a mail account on this device?"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* dismissAction = [UIAlertAction actionWithTitle:#"Dismiss" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {}];
alert.view.tintColor = [UIColor orangeColor];
[alert addAction:dismissAction];
[self presentViewController:alert animated:YES completion:nil];
}
}
You need to arrange your code so that the email part doesn't start until after the file has been completely saved. There are a number of ways to do that:
polling: You can keep checking the status of the file until you find that the operation is complete. Sometimes polling is unavoidable, but it's generally the worst option. Don't go this route.
completion routine: The file saving method could take a parameter that is a block of code to execute when the saving is done. This is very common, and you'll see it all over the place in the various iOS frameworks. For example, NSURLSession has a -dataTaskWithURL:completionHandler: method. When the requested data has been loaded, the completion handler is called.
serial execution: Put your file-saving code in one block and your mail sending code in another block. Then you can use NSOperationQueue or use Grand Central Dispatch directly to schedule both blocks in a serial queue, so that the second block won't start until the first one is finished.
Both the completion routine approach and the serial execution approach are reasonable ways to ensure that you don't start the e-mail process until the file is saved, so pick whichever seems easiest to you.

FMDB Database not working on device, but it is working in Simulator

I have this database that i created, i want to load it using FMDB, but on the simulator i can query select the data, but not on the ipad.
i use this code to check it.
- (void)createDatabase{
//set reference to database here
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Database filename can have extension db/sqlite.
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *appDBPath = [documentsDirectory stringByAppendingPathComponent:#"product.db"];
success = [fileManager fileExistsAtPath:appDBPath];
if (success) {
[self getAllData];
return;
}
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"product.db"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:appDBPath error:&error];
NSAssert(success, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
[self getAllData];
}
//method used to create database and check if it exists
- (void)getAllData {
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:#"product.db"];
database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = #"SELECT * FROM Categories";
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next]) {
NSString *strID = [NSString stringWithFormat:#"%d",[resultsWithNameLocation intForColumn:#"id"]];
NSString *strName = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"cat"]];
NSString *strLoc = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"Location"]];
// loading your data into the array, dictionaries.
NSLog(#"ID = %#, Name = %#, Location = %#",strID, strName, strLoc);
}
[database close];
}
i put the product.db in the supporting files, and i copied it in the Copy Bundle Resources for the targets. Does anyone what i am doing wrong? i am kind of at a dead end here.
Thanks in advance
The problem occurs at the selecting stage, when i select it it returns:
"DB Error: 1 "no such table"
If you ever ran the app on the device before you refined the file open/copy logic, you may have accidentally created a blank database in the documents folder (because if you open a database and it's not found, it creates a blank database). Try deleting the app (which will remove any blank database that might be lingering about) and reinstalling the app and see if that fixes it.
By the way, in the future, you can avoid this problem by using openWithFlags (rather than open) with SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE), and that will ensure that the app never creates a blank database if it doesn't find one.
You can see the SQLite documentation on sqlite3_open (which FMDB open uses) and sqlite_open_v2 (which FMDB openWithFlags uses) for a discussion of these flags.

Creating image thumbnails to be loaded into tableview from files in app directory

I need some help for a problem which I can't solve but I think I am close to the solution. Here is it:
In a version of my app, I was able to populate a tableview (in CellforRowAtIndexPath) with images taken from the Assetlibrary using the following 2 lines:
ALAsset someAsset = [theAssets objectAtIndex:indexPath.row];
[cell.imageView setImage:[UIImage imageWithCGImage:[someAsset thumbnail]]];
Now, I am trying to read image files from the directory and stored in an NSMutableArray. I can see the filenames when I NSLog the NSMutableArray. But how do I now cause the files to be converted into images and displayed in my tableview in the same way I did why using the Assetlibrary? I have tried several times, but the app either crashes or it does not show the image thumbnails in the table view. For example, this statement does not display the image but the app does not crash:
UIImage *anImage = [UIImage imageWithContentsOfFile:[self.listTable objectAtIndex:0]];
When I setImage in the next line, it doesn't work. Someone please help!
UPDATED: The contents of self.listable when I NSLog it is something like this: "19-10-25 276-10-12.jpg",
"19-10-37 276-10-12.jpg",
"19-10-54 276-10-12.jpg",
"19-10-65 276-10-12.mov", etc.
When I NSLog my documents directory, it is: /var/mobile/Applications/8CB1368A-AAE2-4815-BD26-A7B7C8536193/Documents.
Apps can only imageWithContentsOfFile for files located within the app's sandbox (e.g. in Documents, the bundle, etc.) If these files are located in your Documents folder, you get the full path via:
NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *imageFullPath = [documentsFolder stringByAppendingPathComponent:imageFilename];
You can then use that in imageWithContentsOfFile.
Update:
You asked:
my directoryContents is an NSArray; how do I convert it to NSString and then [documentsFolder stringByAppendingPathComponent ...] and store each fullpath in the NSMutableArray?
You might do something like:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSArray *filenames = [fileManager contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSMutableArray *paths = [[NSMutableArray alloc] initWithCapacity:[filenames count]];
for (NSString *filename in filenames)
{
[paths addObject:[documentsDirectory stringByAppendingPathComponent:filename]];
}
NSLog(#"%s filenames = %#", __FUNCTION__, filenames);
NSLog(#"%s paths = %#", __FUNCTION__, paths);

NSFileManager creating folders herarichy & not saving plist [open]

The problem is appearing on device not on simulator.
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Plist1.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSLog(#"documentsDirectory --- %#", documentsDirectory);
NSLog(#"path --- %#", path);
#try
{
if (![fileManager fileExistsAtPath:path])
{
[fileManager copyItemAtPath:documentsDirectory toPath:path error:&error];
}
}
#catch (NSException *exception)
{
NSLog(#" [exception description] -- %#", [exception description]);
}
#finally {
}
[dictEmot writeToFile:path atomically:YES];
// To verify
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSLog(#"[dict allKeys] My plist ----- %#", [dict allKeys]);
Above is the code which I have written to save two plist files in my documents directory. same method I use to save my second plist.
[self savePlist:plist1];
[self savePlist:plist2];
My problem is whenever I try to save second plist it creates hierarchy of folders inside documents directory and also not saves my plist2 file with it contents.
once it complets the 2nd method call, my app documents directory looks like below,
Documents
-> plist1
-> plist1
.
.
.
-> plist1
-> plist1
other files
I treid doing on main thread also but same result.
its not even printing exception.
What is my mistake ?
- Why its creating hierarchy ?
I don't know much about iOS but I think your problem is with stringByAppendingPathComponent:#"Plist1.plist" Check your documentation as I am supscious by the word Appending and even if you send new file name as you are hardcoding the value of the filename to Plist1.plist...
Still I am not sure as I have not done iOS coding for a long time now.

Resources