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;
}
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 want to start off by saying that this block of code worked before the implementation and roll out of iOS 7.
Basically I have a file name that I'm looking for in the NSCachesDirectory, so I create a URL object as my search item. Then I enumerate the directories (using the same NSFileManager object) and look for file names that equal each other, and that's how I know the file exists.
+ (BOOL)itemExistsInMemory:(NSString *)itemName
{
BOOL itemExists = NO;
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSArray *mySandboxDirs = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask];//get the directories for the application
NSURL *searchForURL = [[mySandboxDirs lastObject] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#%#", itemName, FileNameAppendix]];//look for the specific file
NSArray *enumerator = [fileManager contentsOfDirectoryAtURL:[mySandboxDirs lastObject] includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, NSURLIsRegularFileKey, NSURLCreationDateKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
for(NSURL *url in enumerator)
{//find out if any of the URLs within the NSCachesDirectory match what we're looking for
NSNumber *isRegularFile = nil;
[url getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:NULL];
if([isRegularFile boolValue])
{
if([url relativeString] isEqualToString:[searchForURL relativeString]])
{
itemExists = YES;
break;
}
}
}
return itemExists;
}
The file DOES INDEED EXIST in the NSCachesDirectory, however, the code doesn't find it because the searchForURL object was created without a path component (the path component is private/...). Why? Even stranger is that I save the data to a URL that's created with the
[[mySandboxDirs lastObject] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#%#", itemName, FileNameAppendix]];
instruction! So it doesn't include the private/ path component in the URL, but when I write the data with [NSData writeToURL:] it "redirects" it to the private/ directory.
So why can't [NSFileManager URLsForDirectory: inDomains:] get me the correct directories for NSCachesDirectory?
More info moved from the comment for better formatting:
By the way, here's an example of what's contained in the URLs I'm creating and looking for ->
When I create a URL for saving:
file:///var/mobile/Applications/C63B378E-5EBE-417C-A465-8C3A3DCE013A/Library/Caches/Experimental%20Post.cnt
When I create a URL for searching:
file:///var/mobile/Applications/C63B378E-5EBE-417C-A465-8C3A3DCE013A/Library/Caches/Experimental%20Post.cnt
What the enumerator sees:
file:///private/var/mobile/Applications/C63B378E-5EBE-417C-A465-8C3A3DCE013A/Library/Caches/Experimental%20Post.cnt
I could simply take the URLs that the enumerator comes up with and remove the "private" part of the URL so that the strings match and I can proceed, but I'd like to understand why this is happening. Please also note that this only happens when you put the app on an iDevice since the directories are different than when you simulate it with the iOS Simulator.
Thanks to anyone that can be of assistance.
/var is a symlink to /var/private, use [NSURL URLByResolvingSymlinksInPath] to resolve the symlink.
In your example:
for(NSURL *url in enumerator) {
NSURL *resolvedSymlinksURL = [url URLByResolvingSymlinksInPath];
...
}
More detailled discussion: What does the /private prefix on an iOS file path indicate?
in the for loop, see if the url object have some return characters (like \r or \n) and remove it before proceeding.
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.
I'm trying to copy a downloaded file to a specific folder in the app's documents directory but can't seem to get it working. The code I'm using is:
NSString *itemPathString = #"http://pathToFolder/folder/myFile.doc";
NSURL *myUrl = [NSURL URLWithString:itemPathString];
NSArray *paths = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *folderPath = [[paths objectAtIndex:0] URLByAppendingPathComponent:#"folder"];
NSURL *itemURL = [documentsPath URLByAppendingPathComponent:#"myFile.doc"];
// copy to documents directory asynchronously
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *theFM = [[NSFileManager alloc] init];
NSError *error;
[theFM copyItemAtURL:myUrl toURL:itemURL error:&error];
}
});
I can retrieve the file OK but can't copy it. Can anyone tell me if there's anything wrong with the above code?
If downloading a file from a server, if it's a reasonably small file (e.g. measured in kb, not mb), you can use dataWithContentsOfURL. You can use that method to load the file into memory, and then use the NSData instance method writeToFile to save the file.
But, if it's a larger file, you will want to use NSURLConnection, which doesn't try to hold the whole file in memory, but rather writes it to the file system when appropriate. The trick here, though, is if you want to download multiple files, you either have to download them sequentially, or encapsulate the NSURLConnection and the NSOutputStream such that you can have separate copies of those for each simultaneous download.
I have uploaded a project, Download Manager that demonstrates what a NSURLConnection implementation might look like, but it's non-trivial. You might rather want to contemplate using an established, third-party library, such as ASIHTTPRequest or RestKit.
If you want to access a folder with a given name you should check if it exists and if not create it. That could quite easy be done like this:
NSString *folder = [documentsPath stringByAppendingPathComponent:folderName];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if (![fileManager fileExistsAtPath:folder]) {
[fileManager createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:&error];
}
if (error != nil) {
NSLog(#"Some error: %#", error);
return;
}
EDIT
If you want to check if the folder was created properly on your device got to Organizer -> Devices -> [YourDevelopingDeviceWhereTheAppWasInstalled] -> Applications -> [YourApplication]
In the lower section you should at least see some folders like Documents. And if successful your created folders as well.
You need to create any intermediate directories prior to copying files. Check in the Simulator folder to see wether the "folder" directory is created in the applications Documents-folder.
Path to simulator is /Users/$username/Library/Application Support/iPhone Simulator/
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString* directory = [url path];
NSString* filePath = [directory stringByAppendingPathComponent:FILE_NAME];
if ([fileManager fileExistsAtPath:filePath])
{
[fileManager removeItemAtPath:filePath error:nil];
}
Here's my code. When it is executed, the file is deleted, but the space remains occupied. Here's the code for storing something into the file.
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString* directory = [url path];
NSString* filePath = [directory stringByAppendingPathComponent:FILE_NAME];
NSArray* oldArray = nil;
if ([fileManager fileExistsAtPath:filePath])
{
oldArray = [[NSArray alloc] initWithContentsOfFile:filePath];
[fileManager removeItemAtPath:filePath error:nil];
}
NSMutableArray* mergeArray = [[NSMutableArray alloc] initWithArray:arrayOfPersons];
[mergeArray addObjectsFromArray:oldArray];
if ( [mergeArray writeToFile:filePath atomically:YES]) NSLog(#"Written");
By the way, it cost 1 MB to store an array with only 1 object(an NSDictionary with 2 keys). Is there a cheaper way to store it?
You need to be much more careful with your experiments. The unix file system does lots of stuff with files. In fact, when you "delete" a file, all you do is unlink it from the file system. If that file is open with another file descriptor, anywhere in the OS, it will remain open.
Furthermore, there are lots of optimizations to reuse file nodes. Just because you delete a file, does not mean that space goes back automatically. It could be "reserved" in your app for several reasons, for other files to use. No sense giving it back to the file system until the file system needs it.
settings->general->usage is a very rough measurement of file system utilization. A better measurement would be accessing the attributes of the file and file system directly.
Using your code as a base, consider this:
- (NSString*)workingDirectory {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* url = [[fileManager URLsForDirectory:NSCachesDirectory
inDomains:NSUserDomainMask] lastObject];
return [url path];
}
- (NSString*)filePath {
return [[self workingDirectory] stringByAppendingPathComponent:FILE_NAME];
}
Now, you can see all the attributes of the entire file system with this:
NSDictionary *attributes = [[NSFileManager defaultManager]
attributesOfFileSystemForPath:[self workingDirectory] error:0];
NSLog(#"file system attributes: %#", attributes);
and those for the specific file with this:
NSDictionary *attributes = [[NSFileManager defaultManager]
attributesOfItemAtPath:[self filePath] error:0];
NSLog(#"file attributes: %#", attributes);
Pay attention to NSFileSystemFreeSize and NSFileSize.
Run your app, and dump both of these values. Create your file, and dump them again. Delete the file, and dump them again.
After all that, you may actually see the NSFileSystemFreeSize go UP, even after the delete. Remember, the system itself is creating temporary files, and is probably caching those file system nodes for future use.
You can get more consistent results if you quit all other apps. Then, quit yours (double-click power button, X all running apps). Delete the file before doing this.
Now, start your app, without the file existing.
Dump file system data.
Create the file.
Dump file system data.
Dump file data.
You should see the file taking up about 200-250 bytes, and the file system free size should drop 8192.
Delete the file.
Dump file system data. Is probably at least as big as it was before deleting file.
Quit app (not in XCode -- double-click power, X the app).
Run the app.
Dump file system data. You should see the data back to about what it was when you started earlier.
In conclusion, while it may look like the file system has not released the data, it really has, but maybe the tool you are using to query just does not know the details of the file system.
Note, also, that when an app is running, it will use lots of file system resources for stuff that you are not explicitly doing.
I hope that made sense...
Your code to delete the file looks correct, but you are switching between URLs and Paths when you don't need to. You should also be checking for an error when you try to delete the file so that you can see why it doesn't work. Try this:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *directoryURLs = [fileManager URLsForDirectory:NSCachesDirectory
inDomains:NSUserDomainMask];
NSURL *directoryURL = [directoryURLs objectAtIndex:0];
NSURL *fileURL = [directoryURL URLByAppendingPathComponent:FILE_NAME];
if (!fileURL)
{
NSLog(#"Could not create URL for file.");
return;
}
NSError *err = nil;
if (![fileURL checkResourceIsReachableAndReturnError:&err])
{
NSLog(#"File is not reachable.\n"
"Error: %# %d %#", [err domain], [err code], [[err userInfo] description]);
return;
}
err = nil;
[fileManager removeItemAtURL:fileURL error:&err];
if (err)
{
NSLog(#"Unable to delete existing file.\n"
"Error: %# %d %#", [err domain], [err code], [[err userInfo] description]);
return;
}
May it be, that the length of the ˚FILE_NAME` is greater or equal to 300 chars? This brought me to similar issues with NSFileManager some time ago...