EDIT: Have checked and rechecked file paths, code for archiving and unarchiving and no bugs found. Is there an issue with App File System between launches that I am not aware of?
I am using NSCoder to store images in my app. I create a unique file path, store that file path to Core Data, and archive an image using that same file path in the documents.
On the app's first launch, everything works as expected - the file paths saved in Core Data are used to unarchive the images stored in Documents. On subsequent launches, however, the unarchiver returns nil, and I get a crash.
ImageForArchiving.m - the object that is to be saved with init and encode methods
+ (ImageForArchiving *)createImageWithImage:(NSData*)data andDate:(NSDate*)date {
ImageForArchiving *archiveImage = [[ImageForArchiving alloc] init];
[archiveImage setDate:date];
[archiveImage setImageData:data];
return archiveImage;
}
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
[self setDate: [coder decodeObjectForKey:#"date"]];
[self setImageData: [coder decodeObjectForKey:#"image"]];
}
return self;
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:self.date forKey:#"date"];
[coder encodeObject:self.imageData forKey:#"image"];
}
Saving to archive, Core Data, creation of file path and unarchiving code
- (void)saveItemsAtFilePath:(NSString*)filePath andImageToArchive:(ImageForArchiving*)imageRecord {
[NSKeyedArchiver archiveRootObject:imageRecord toFile:filePath];
}
- (void)loadItemsWithFilePath:(NSString*)filePath {
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
ImageForArchiving *image = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"image %#", image.date);
} else {
NSLog(#"nothing saved");
}
}
-(void)saveToCoreDataWithFilePath:(NSString*)filePath{
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = delegate.managedObjectContext;
NSManagedObject *photoAndDate = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:context];
[photoAndDate setValue:[NSDate date] forKey:#"date"];
[photoAndDate setValue:filePath forKey:#"image"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
- (NSString *)createPathForDataFile
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
documentsPath = [documentsPath stringByExpandingTildeInPath];
NSError *error = nil;
if ([fileManager fileExistsAtPath: documentsPath] == NO)
{
[fileManager createDirectoryAtPath:documentsPath withIntermediateDirectories:YES attributes:nil error:&error];
}
NSString *stringForSaving = [[NSUUID UUID]UUIDString];
NSString *filePath = [documentsPath stringByAppendingPathComponent:stringForSaving];
return filePath;
}
Looping through Core Data array to load up data source. The addition of the unarchived object is w
I was encountered the same problem before. You write a image to a path and save it to the core data. Later read the image path, it is exactly same but no image is loaded.
So, Did you tested the app in the simulator? I don't know if it is intended or a kind of a bug but it would be working as you wanted on the real devices.
So try to run your app in the real devices and see if it works.
I just found out that in iOS 8 the file paths are reset after each app launch. The code above save the file name to Core Data and re-create the file path to Documents when calling for the file.
For instance, the below would create the file name.
- (NSString *)createPathForDataFile
{
NSString *stringForSaving = [[NSUUID UUID]UUIDString];
NSLog(#"filePath in CreatePathForDataFile %#", stringForSaving);
return stringForSaving;
}
Saving would occur by calling the path to the Documents folder and passing in the file name as the parameter filePath.
- (void)saveItemsAtFilePath:(NSString*)filePath andImageToArchive:(ImageForArchiving*)imageRecord {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *finalFilePath = [documentsPath stringByAppendingPathComponent:filePath];
[NSKeyedArchiver archiveRootObject:imageRecord toFile:finalFilePath];
}
Related
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
iOS clear caches ,I delete the unuseful files use NSFileManager, but when I do it in simulator, it can clear all unuseful files, when I do it with a real iPhone(iPhone 5s),it was invalid, who can help me to solve this question? thank you very much!
code:
#define docPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
#define cachePath [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]
- (void)clearCaches
{
[self clearCachesWithFilePath:cachePath];
NSArray *docFileArr = [self getAllFileNames:docPath];
for (NSString * fileName in docFileArr) {
if (![fileName isEqualToString:#"mqtt.cer"]) {
[self clearCachesWithFilePath:[docPath stringByAppendingString:[NSString stringWithFormat:#"/%#", fileName]]];
}
}
}
- (NSArray *)getAllFileNames:(NSString *)dirPath{
NSArray *files = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:dirPath error:nil];
return files;
}
- (BOOL)clearCachesWithFilePath:(NSString *)path{
NSFileManager *mgr = [NSFileManager defaultManager];
return [mgr removeItemAtPath:path error:nil];
}
You are trying to delete the caches directory itself, instead get the list of contents in the caches directory and remove them one by one. Hope this helps.
I am slightly confused why the following doesn't work. Can someone enlighten me?
I have one function that returns a filePath:
- (NSString *) lastLocationPersistenceFilePath {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"last_location"];
return filePath;
}
Then, in a method triggered by a button, I attempt to save a CLLocation object like so:
// Store this location so it can persist:
BOOL success = [NSKeyedArchiver archiveRootObject:_startLocation toFile:[self lastLocationPersistenceFilePath]];
if (!success) {
NSLog(#"Could not persist location for some reason!");
} else {
NSLog(#"New location has been stored.");
}
In my viewDidLoad:, I attempt to load it:
- (void)viewDidLoad
{
[super viewDidLoad];
// Retrieve last location:
CLLocation *decodedLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:[self lastLocationPersistenceFilePath]];
if (decodedLocation) {
_startLocation = decodedLocation;
} else {
NSLog(#"Decoding failed.");
}
}
The archiving fails which, of course, means I can't decode anything either. I feel like I am missing something obvious/trivial... any pointers?
Simply, it is not possible to write to the the app's resource directory. Instead write to the app's document directory.
Code example"
- (NSString *) lastLocationPersistenceFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths firstObject];
NSString *filePath = [documentDirectory stringByAppendingPathComponent:#"last_location"];
return filePath;
}
I've been attempting to get my app to save and load data from a set of arrays in the app, the odd issue is that when the app terminates (completely shut down) the data does not seem to load upon being restarted, i've had a look over a lot of posts, tutorials etc but i can't seem to get it to work, i have two test buttons on the app to trigger the save and load methods and also a button to clear the records, when i use these to test, it works perfectly and the data saves and loads correctly.
My current setup is as follows:
I have a plist file called data.plist in the supporting files directory, inside this file i have the various arrays with the same data in index 0 as the data that is initialised when the global data class creates an instance of itself.
My save code:
- (void)saveData{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
// create dictionary with arrays and their corresponding keys
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personalitySliderValue, looksSliderValue, humourSliderValue, chemistrySliderValue, emptySlider, notesValues,nameValues, noValues, ratingValues, nil] forKeys:[NSArray arrayWithObjects: #"personality", #"looks", #"humour", #"chemistry",#"empty",#"notes",#"name",#"no",#"rating", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check if plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", error);
}
}
My load code:
- (void)loadData{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"data.plist"];
// check to see if data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle CHECK D capitalisation
plistPath = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property list into dictionary object
NSDictionary *dictionaryTemp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!dictionaryTemp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
personalitySliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"personality"]];
looksSliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"looks"]];
humourSliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"humour"]];
chemistrySliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"chemistry"]];
emptySlider = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"empty"]];
notesValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"notes"]];
nameValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"name"]];
noValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"no"]];
ratingValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"rating"]];
}
And finally the app delegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// save the app data
[[GlobalData sharedGlobalData]saveData];
NSLog(#"save method run");
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
// load the app data
[[GlobalData sharedGlobalData]loadData];
NSLog(#"load method run");
}
This has literally been making me pull my hair out, so any help would be great!
You can load data at launch time in this app delegate method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
....
[[GlobalData sharedGlobalData] loadData];
}
In my game, when a level is completed the app stores a "1" in a file in the Documents directory of the app. When the game then loads, a player can only play a level if the previous level has been completed. When I test the game via Xcode and on a device the app works properly and a level cannot be played until the previous level has been completed. However, when the app was approved and released on the App Store, the app behaves as if each level has been completed (no locked levels). I can't figure this one out and would appreciate someone's help! The devices I'm testing on are all iOs 5.0 or higher.
Below is the code that saves the completed level in the Documents directory:
NSMutableData* data = [[NSMutableData alloc] init];
NSKeyedArchiver* coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSString *levelString = [NSString stringWithFormat:#"Level%d",level];
[coder encodeInteger:1 forKey:levelString];
[coder finishEncoding];
NSString *levelString2 = [NSString stringWithFormat:#"Level%d.save",level];
///
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:levelString2];
// Check if the file already exists
if ([filemgr fileExistsAtPath: dataFile])
{
[[NSFileManager defaultManager] removeItemAtPath:dataFile error:nil];
}
[data writeToFile:dataFile atomically:YES];
[coder release];
[data release];
}
#catch (NSException* ex)
{
CCLOG(#"level save failed: %#", ex);
}
Below is the code that reads the Document directory to see if the level has been completed:
if ([self loadCompletedLevels:6] == 1) { //// level gets unlocked **** }
-(int) loadCompletedLevels:(int)theLevel; {
int isLevelCompleted; //1 = completed
NSString* kSaveFile = [NSString stringWithFormat:#"Level%d.save",theLevel];
NSString *levelString = [NSString stringWithFormat:#"Level%d",theLevel];
#try
{
NSFileManager *filemgr;
NSString *dataFile;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Identify the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
dataFile = [docsDir stringByAppendingPathComponent:kSaveFile];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataFile])
{
NSData* data = [[NSData alloc] initWithContentsOfFile:dataFile];
if (data && [data length] > 0)
{
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
isLevelCompleted = [decoder decodeIntForKey:levelString];
[decoder release];
}
[data release];
}
if (isLevelCompleted == 1) {
levelCompleted = YES;
}
}
#catch (NSException* ex)
{
levelCompleted = NO;
}
return isLevelCompleted; }
You should probably use a different approach for storing your data, but the real problem is that you are not initializing the return value, isLevelCompleted. It is on the stack, and does not have a default value. It starts out with whatever happens to be at that stack location.
So, if you don't set it, it will have an arbitrary value.
Also, you should probably use BOOL for a boolean value, but if you make this:
int isLevelCompleted = 0; //1 = completed
you will initialize it to "false" so it must be explicitly changed to "true" by your code.