In my game, I'm saving stats of the player in a plist that I store in the Documents directory. I have an empty dictionary of each stats that should be saved named "Default_Stats.plist" so that if it's the first time the app is loaded, it will copy it in the appropriate directory so it could be loaded and overwritten at will. The problem is, every time my app is loaded, it doesn't recognize the "Stats.plist" and overwrite it with the Default Stats, resetting every stats the player have made... And weird enough, it was perfectly working on the simulator, but not on the device. Here's my code :
In this method I read the stats :
- (void) readStatsFromFile{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *statsPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Stats.plist"];
//Check if the file has already been created
if (![[NSFileManager defaultManager] fileExistsAtPath:statsPath]){
[self createStatsList];
}else{
stats = [[NSMutableDictionary dictionaryWithContentsOfFile:statsPath]retain];
}
}
Here's my creating method :
- (void) createStatsList{
NSString *statsPath = [[NSBundle mainBundle] bundlePath];
statsPath = [statsPath stringByAppendingPathComponent:#"Default_Stats.plist"];
stats = [[NSMutableDictionary dictionaryWithContentsOfFile:statsPath] retain];
[self writeStatsToFile];
}
And my writing method :
- (void) writeStatsToFile{
BOOL ok;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *statsPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Stats.plist"];
ok = [stats writeToFile:statsPath atomically:YES];
if (!ok) {
NSLog(#"Couldn't write to file");
}else
NSLog(#"Stats written succesfully!");
}
Please help, I really don't understand what's wrong! I hope I've made myself clear enough!
Use filepath instead of absolute path.
Maybe duplicates exist in your mac, which makes exists=true on simulator, but not on device.
The easiest way to check would be to NSLog the paths encountered. Refer to these tools - they allow console logs to be captured for release builds running on your device.
Most likely that your documents directory just doesn't exist - on the simulator you share a documents directory with everyone on the Mac; on the device everyone has his own directory. Use the file manager method
createDirectoryAtURL:url withIntermediateDirectories:YES
to make sure that the directory is there before you try writing there. (I tend to use the URL methods instead of the file path methods).
PS. I'd recommend having one method that returns the path or url that you want. It's a good habit not to duplicate your code again and again.
I would do pretty much that, like everything in one session:
gets the URL for the file in the Document folder;
if the file is not there yet, copies the file from bundle to the Documents folder;
that should be the method for that, I have defined some macros for avoiding mistyping the file's name in the code:
- (NSURL *)statsFileURL {
#define NSStringFromFileNameWithExtension(filename, extension) [(filename) stringByAppendingPathExtension:(extension)]
#define kExtension #"plist"
#define kDefaultStatsFileName #"Default_Stats"
#define kCustomStatsFileName #"Stats"
NSURL *_returnURL = nil;
NSFileManager *_fileManager = [NSFileManager defaultManager];
NSURL *_documentDirectory = [[_fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *_myFileURLInDocumentFolder = [_documentDirectory URLByAppendingPathComponent:NSStringFromFileNameWithExtension(kDefaultStatsFileName, kExtension)];
if ([_fileManager fileExistsAtPath:[_myFileURLInDocumentFolder path]]) {
_returnURL = _myFileURLInDocumentFolder;
} else {
NSURL *_myFileURLInBundle = [[NSBundle mainBundle] URLForResource:kDefaultStatsFileName withExtension:kExtension];
if ([_fileManager fileExistsAtPath:[_myFileURLInBundle path]]) {
NSError *_error = nil;
if ([_fileManager copyItemAtURL:_myFileURLInBundle toURL:_myFileURLInDocumentFolder error:&_error]) {
if (_error == nil) {
_returnURL = _myFileURLInDocumentFolder;
} else {
// some error during copying
}
} else {
// some error during copying
}
} else {
// the file does not esixts at all, not even in the bundle
}
}
return _returnURL;
}
the URL always points inside the Documents folder, so you will have read/write access to the file – or will be nil if some error happens.
after you have the URL, you can restore back to file without any issue, and at some other point in runtime you can override the file for your convenience anytime.
NOTE: you may need to extend this code for a more detailed error handling, I put the comment only the places when you need to worry about potential errors.
Related
I am saving video/image in document directory.Now once the image is saved in document directory I want to save its reference in my local database.So I am thinking I can save URL of the image in the local database.
So is it constant throughout my app?
It's not constant, i have observed every time you launch the app it'll be different, but your data is moved to this new path. You can save your file name in your database, and dynamically append this file name to NSDocument directory.
- (NSString *)documentsFilePath:(NSString *)fileName {
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths firstObject];
NSString *filePath = [docsDir stringByAppendingPathComponent:fileName];
return filePath;
}
- (void)storeFile:(NSString *)fileName {
NSString *filePath = [self documentsFilePath:fileName];
// create if needed
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// Write your data to file system here...
}
}
- (void)deleteFile:(NSString *)fileName {
NSString *filePath = [self documentsFilePath:fileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError *deleteErr = nil;
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&deleteErr];
if (deleteErr) {
NSLog(#"Can't delete %#: %#", filePath, deleteErr);
}
}
}
Please handle nil checks and store only filename in DB
No, it's not constant. Whenever your app reinstall or updated on device the document directory will change, because when app installed on device os made an directory for app with some random id and each install this random it get changed by OS.
So, you need to make it dynamic own your own, like store the file name only and append the document directory path while using it.
I would suggest only saving the filename or subdirectory/filename (if you have a subdirectory) in the database and then only attaching that to the NSDocumentDirectory.
This will ensure that you always know where the file is...
NSDocumentDirectory is however consistent accross updates, so the files should remain in the document directory even if you update...
I have a folder in image.xcassets which has more than 50 images for both iPhone and iPad.
I don't want to hard code all the names programatically. Can I get the list of images in that folder in an NSArray?
I'm not sure if this fully answers your question, but should you normally do this with the method
- (NSArray *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask error:(NSError **)error ?
This is a code snippet from a program I wrote to get all the images from a directory:
-(void)getContentOfImageDirectory
{
//Emptying the image directory content array
[_imageDirectoryContent removeAllObjects];
//Using NSFileManager to load the content of the image directory in a temporary array
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *tempArray = [fm contentsOfDirectoryAtURL: _imageDirectory includingPropertiesForKeys: _imageProperties options: NSDirectoryEnumerationSkipsPackageDescendants error: nil];
//Copy the temporary array into the imageDirectoryContent before returning the NSMutableArray imageDirectoryContent
[_imageDirectoryContent addObjectsFromArray:tempArray];
}
The variable _imageProperties is just an array of "common file system resource keys" as Apple calls them. The variable _imageDirectory is the URL from which you want to get the files.
hope this helps.
I'm sorry to annoy you and misunderstood your question. However, if I use the URL file///User/<Your Userid>/your file path to the program/Images.xcassets/ I get the content of that directory.
On the other hand if I use URLsForDirectory:NSApplicationDirectory inDomains:NSUserDomainMask
and then
URLByAppendingPathComponent:#"Your Application container/Contents/Resources"
I can read all the image files of any fully compiled and operational application. I'm not aware of determining the application's resource folder in any other way.
This is a code snippet for accessing the resources directory of the Windows 7 applications folder from parallels.
-(id)initWithStartURL
{
self = [super init];
if(self)
{
//Initiating the file manager
NSFileManager *fm = [NSFileManager defaultManager];
//Getting the applications directory
NSArray *listOfURLs = [fm URLsForDirectory:NSApplicationDirectory inDomains:NSUserDomainMask];
if([listOfURLs count] >= 1)
{
_tempDirectory = [listOfURLs objectAtIndex:0];
}
_imageDirectory = [_tempDirectory URLByAppendingPathComponent:#"Windows 7 Applications.app/Contents/Resources"];
}
return self;
}
As i am downloading a video in my app and keeping it in local cache/Document path and showing when necessary. It is working in iOS 7 but the avplayer not showing video in iOS 8 and above. As i have read that the document/cache path is changed on every launch in iOS 8. The issue is, I have to download video once and show it multiple times in my app. So how can i reach the same path again and again to show video in app.
Here is my code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// NSLog(#"Document folder: %#", paths);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSLog(#"Document folder: %#", documentsDirectory);
In The log I am getting different path on each launch. Any Help would be appreciated. Thanks
I got the answer. As the absolute path is changing on every launch, we can save the data on relative path and retrieve it on appending absolute path and relative path.
This is how we can save the data on the relative path:
NSString *documentsDirectory = #"MyFolder";
documentsDirectory = [documentsDirectory stringByAppendingPathComponent:filename];
NSString *relativePath = documentsDirectory;
But when you read the file you have to use absolute path + relative path:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *fullCachePath = ((NSURL*)[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] ).path;
NSString *fullPath = [fullCachePath stringByAppendingPathComponent:relativePath];
For Database also store data on the relative path only. But while reading take the absolute path and append the relative path coming from database and read.
Its Working here.
The path to the application container or sandbox changing should be an expected condition. You should not store the absolute filesystem path to a sandbox directory; instead store the path relative to that directory, and append that to the result of NSSearchPathForDirectoriesInDomains each time.
It is recommended to use bookmarks to reference file system resources.
If you want to save the location of a file persistently, use the bookmark capabilities of NSURL. A bookmark is an opaque data structure, enclosed in an NSData object, that describes the location of a file. Whereas path- and file reference URLs are potentially fragile between launches of your app, a bookmark can usually be used to re-create a URL to a file even in cases where the file was moved or renamed.
A bookmark provides a persistent reference to a file-system resource. When you resolve a bookmark, you obtain a URL to the resource’s current location. A bookmark’s association with a file-system resource (typically a file or folder) usually continues to work if the user moves or renames the resource, or if the user relaunches your app or restarts the system.
For a sample code, checkout the File System Programming Guide -> Locating Files Using Bookmarks
If you want use URL directly for backward compatibility, I have a fix code:
#interface NSURL (App)
/**
This method try to rebuild a file url relative to application’s home directory.
If the reciver is not a file url or not in the home directory. The original URL returns.
#code
NSString *homePath = NSHomeDirectory();
// Assume "/Something/Application/B383551F-41C1-4E3D-8EA9-8D76E4AFA919"
NSURL *test;
test = [NSURL fileURLWithPath:homePath];
[test URLByResolvingApplicationDirectoryChange];
// file://Something/Application/B383551F-41C1-4E3D-8EA9-8D76E4AFA919
test = [NSURL URLWithString:#"file:///Foo/bar"];
[test URLByResolvingApplicationDirectoryChange];
// file:///Foo/bar
test = [NSURL URLWithString:#"file://Something/Application/12345678-1234-1234-1234-123456789ABC/path.tmp"];
// file://Something/Application/B383551F-41C1-4E3D-8EA9-8D76E4AFA919/path.tmp
#endcode
*/
- (nonnull NSURL *)URLByResolvingApplicationDirectoryChange;
#end
#implementation NSURL (App)
- (nonnull NSURL *)URLByResolvingApplicationDirectoryChange {
if (!self.isFileURL) return self;
NSString *pathHome = NSHomeDirectory();
NSString *pathThis = self.path;
if ([pathThis hasPrefix:pathHome]) {
return self;
}
NSArray<NSString *> *cpHome = pathHome.pathComponents;
NSMutableArray<NSString *> *cpThis = pathThis.pathComponents.mutableCopy;
if (cpThis.count < cpHome.count) {
return self;
}
NSInteger i = 0;
for (i = 0; i < cpHome.count - 2; i++) {
NSString *hp = cpHome[i];
NSString *tp = cpThis[i];
if (![hp isEqualToString:tp]) {
return self;
}
}
i++;
NSString *hp = cpHome[i];
NSString *tp = cpThis[i];
if (hp.length != tp.length) {
return self;
}
[cpThis replaceObjectAtIndex:i withObject:hp];
NSString *resolvedPath = [NSString pathWithComponents:cpThis];
return [NSURL.alloc initFileURLWithPath:resolvedPath];
}
#end
I have a file located in my app's Documents folder. When the app is terminated I save the file's URL in the AppDelegate's applicationWillTerminate method:
// archiver init code
[archiver encodeObject:file.URL forKey:kFileURL];
// finish encoding and write data to file system
But when trying to restore the file on the next app launch the file manager cannot locate the file: After calling
NSURL *fileURL = [unarchiver decodeObjectForKey:kFileURL];
NSString *filePath = fileURL.path;
the method
[[NSFileManager defaultManager] fileExists:filePath];
returns NO.
I tried to find the reason for this and I discovered that the path to the Documents folder changes with every app launch. The part that changes is the hexadecimal folder in the middle. Here are two examples:
/private/var/mobile/Applications/04083A4A-87AC-4E3C-8BA1-F002B97AE304/Documents/...
/private/var/mobile/Applications/65D136BA-42C3-887A-B947-7FE396978153/Documents/...
I always thought that the hexadecimal part is some sort of ID unique to every app. But as it changes: What exactly is that number?
And how can I relocate my file then after terminating and relaunching my app?
You should just get the directory for the document folder and then load your file.
+ (NSString *)documentDataPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if ([paths count] == 0) {
return nil;
}
NSString *directory = [paths objectAtIndex:0];
if (directory == nil) {
NSLog(#"NSDocumentDirectory not found!");
}
return directory;
}
I am donwloading and retrieving files with ASIHTTPRequest. It already works, but now I am trying to break the download, when it is already stored. I can't figure out, how to implement this problem. I would like to solve it with an if-clause: if the file is not cached, download it else break. Would you mind to help me writing a proper Objective-C code? I have the file path, if I need it so compare or look after that file.
Thanks in advance!
pseudo code :
-(IBAction) download : (id) sender {
if (data1.pdf) {
// the download algorithm
}
else
break;
}
If you mean how you can check if a file is stored in the documents directory of your device, then you can use:
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* file = [documentsPath stringByAppendingPathComponent:#"data.pdf"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
...