NSFileManager in IOS - ios

- (void)viewDidLoad
{
[super viewDidLoad];
NSFileManager * fileManager =[[NSFileManager alloc]init];
NSArray *Apath=[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSString *FilePath=[[Apath objectAtIndex:0] absoluteString];
NSString *TEST =[FilePath stringByAppendingPathComponent:#"test10.txt"];
BOOL flage =[[NSFileManager defaultManager] fileExistsAtPath:TEST];
if (flage)
{
NSLog(#"It's exist ");
}
else
{
NSLog(#"It is not here yet ");
NSData * data =[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:#"www.google.com"]];
[data writeToFile:TEST atomically:YES];
}
}
I'm just trying to create a text file , it always give me "It is not here yet"
Anything wrong with the code ??

You haven't created a file, you have only created a file path: you need to write a file to that path.
//after these lines ... (I've improved your variable names)
NSArray *paths=[fileManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask];
NSString *filePath=[[paths objectAtIndex:0] absoluteString];
filePath =[filePath stringByAppendingPathComponent:#"test10.txt"];
//try this
NSString* fileContent = #"some text to save in a file";
BOOL success = [fileContent writeToFile:filePath
atomically:YES
encoding:NSUTF8StringEncoding
error:nil]
But watch out - file existence tests come with a warning from Apple:
Note: Attempting to predicate behavior based on the current state of the file system or a particular file on the file system is not recommended. Doing so can cause odd behavior or race conditions. It's far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed.

Related

How to check to see if same exact file exists

I am currently using this code to copy my SQLite database, however it is currently only checking to see if the file exists... I want to change it to check if the file isn't exactly the same, for example I am worried if a database gets corrupt or doesn't copy all the way, the app will lose functionality and the only way to fix this would be to delete the App and redownload it.
So how can I compare if two files are exactly equal?
- (void) copyDatabaseIfNeeded {
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDBPath];
BOOL success = [fileManager fileExistsAtPath:dbPath];
//NSLog(#"%d",success);
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database01.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
- (NSString *) getDBPath
{
//Search for standard documents using NSSearchPathForDirectoriesInDomains
//First Param = Searching the documents directory
//Second Param = Searching the Users directory and not the System
//Expand any tildes and identify home directories.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
//NSLog(#"dbpath : %#",documentsDir);
return [documentsDir stringByAppendingPathComponent:#"database01.sqlite"];
}
You can use contentsEqualAtPath:andPath: method of NSFileManager for this purpose.
Use your code something like this:
......
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database01.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
success = [fileManager contentsEqualAtPath:defaultDBPath andPath:dbPath]; //verify if file size and content matches
if(!success) {
//report error
}
}
.......
And it should do the trick for you.
Edit - Forget this answer - use the one by Ayan.
Start by comparing the file sizes. If the sizes are different you know the files are not the same. This is a simple and quick check.
If the sizes are the same then you need to compare the files, byte by byte. An inefficient way would be to load both files into NSData objects and see if they are equal. This only works if the files will always be small enough to fit in memory.
A better approach is to open both files as streams and read them in chunks. Compare each chunk (say 2k each) until two chunks are different or you get to the end.

Not able to create files in caches directory

I'm trying to set up a caches directory for use in my app, but the files are not being created for a reason unknown to me. What am I doing wrong? Here are the methods I'm using:
In class Utilities:
+(NSString *)imageCachePath {
NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pieceImagesDirectory = [cacheDirectory stringByAppendingPathComponent:#"PieceImages"];
NSLog(#"imageCachPath is %#",pieceImagesDirectory);
return pieceImagesDirectory;
}
+ (void)cacheImage:(UIImage *)image usingName:(NSString *)name;
{
NSLog(#"Caching image %#",name);
NSString *pieceImagesDirectory = [self imageCachePath];
BOOL isDir = NO;
NSError *error;
if (! [[NSFileManager defaultManager] fileExistsAtPath:pieceImagesDirectory isDirectory:&isDir] && isDir == NO) {
[[NSFileManager defaultManager]createDirectoryAtPath:pieceImagesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
NSLog(#"Error after creating directory:\n%#",error);
} else {
// file exists - I don't expect to use the else block. This is for figuring out what's going on.
NSLog(#"File %# exists -- is it a directory? %#",pieceImagesDirectory, isDir?#"YES":#"NO");
}
NSString *nameToUseInFilename = [name stringByReplacingOccurrencesOfString:#" " withString:#"_"];
NSString *fullPath = [pieceImagesDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png",nameToUseInFilename]];
NSData *data = UIImagePNGRepresentation(image);
NSFileManager *fileManager = [NSFileManager defaultManager];
//Save the file, overwrite existing if exists.
NSLog(#"Attempting to create file at path %# with %d bytes of data",fullPath, [data length]);
if ([fileManager createFileAtPath:fullPath contents:data attributes:nil]) {
NSLog(#"Success");
} else {
NSLog(#"Error creating file");
}
}
In the class where the images are created, I call the method thus:
// image is an object of type UIImage
// cachedImageName is a string that resolves to something like User Image/12
[Utilities cacheImage:image usingName:cachedImageName];
Here are sample NSLog output lines in the debugger:
... Caching image User Image/12
... imageCachPath is /var/mobile/Applications/5EBB1152-5CC1-4A30-ABD5-B4C9A60E4CB4/Library/Caches/PieceImages
... File /var/mobile/Applications/5EBB1152-5CC1-4A30-ABD5-B4C9A60E4CB4/Library/Caches/PieceImages exists -- is it a directory? YES
... Attempting to create file at path /var/mobile/Applications/5EBB1152-5CC1-4A30-ABD5-B4C9A60E4CB4/Library/Caches/PieceImages/User_Image/12.png with 12071 bytes of data
... Error creating file
The call to NSFileManager fileExistsAtPath:isDirectory: gives you an indeterminate value for isDir if the directory doesn't exist. You should change your code to:
BOOL isDir = NO;
NSError *error;
if (! [[NSFileManager defaultManager] fileExistsAtPath:pieceImagesDirectory isDirectory:&isDir]) {
[[NSFileManager defaultManager]createDirectoryAtPath:pieceImagesDirectory withIntermediateDirectories:YES attributes:nil error:&error];
NSLog(#"Error after creating directory:\n%#",error);
} else {
// file exists - I don't expect to use the else block. This is for figuring out what's going on.
NSLog(#"File %# exists -- is it a directory? %#",pieceImagesDirectory, isDir?#"YES":#"NO");
}
It also appears that you add a second folder User_Image to the path. You never create this directory.
I would also suggest you change how you write the image data. Instead of using NSFileManager createFileAtPath:contents:attributes:, use NSData writeToFile:options:error:. Then you can get an error object providing more details of any problem.
In the end it may be best to build the full path to the file. Strip off the last path component (the actual filename) and then check for the existence of the remaining path. Then make one call to createDirectoryAtPath... and let it create any all all needed folders.

writing string to txt file in objective c

Pulling my hair out trying to work this out. i want to read and write a list of numbers to a txt file within my project. however [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error] doesnt appear to write anything to the file. I can see there is the path string returns a file path so it seems to have found it, but just doesnt appear to write anything to the file.
+(void)WriteProductIdToWishList:(NSNumber*)productId {
for (NSString* s in [self GetProductsFromWishList]) {
if([s isEqualToString:[productId stringValue]]) {
//exists already
return;
}
}
NSString *string = [NSString stringWithFormat:#"%#:",productId]; // your string
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSError *error = nil;
[string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", error.localizedFailureReason);
// path to your .txt file
// Open output file in append mode:
}
EDIT: path shows as /var/mobile/Applications/CFC1ECEC-2A3D-457D-8BDF-639B79B13429/newAR.app/WishList.txt so does exist. But reading it back with:
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
returns nothing but an empty string.
You're trying to write to a location that is inside your application bundle, which cannot be modified as the bundle is read-only. You need to find a location (in your application's sandbox) that is writeable, and then you'll get the behavior you expect when you call string:WriteToFile:.
Often an application will read a resource from the bundle the first time it's run, copy said file to a suitable location (try the documents folder or temporary folder), and then proceed to modify the file.
So, for example, something along these lines:
// Path for original file in bundle..
NSString *originalPath = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSURL *originalURL = [NSURL URLWithString:originalPath];
// Destination for file that is writeable
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *documentsURL = [NSURL URLWithString:documentsDirectory];
NSString *fileNameComponent = [[originalPath pathComponents] lastObject];
NSURL *destinationURL = [documentsURL URLByAppendingPathComponent:fileNameComponent];
// Copy file to new location
NSError *anError;
[[NSFileManager defaultManager] copyItemAtURL:originalURL
toURL:destinationURL
error:&anError];
// Now you can write to the file....
NSString *string = [NSString stringWithFormat:#"%#:", yourString];
NSError *writeError = nil;
[string writeToFile:destinationURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", writeError.localizedFailureReason);
Moving forward (assuming you want to continue to modify the file over time), you'll need to evaluate if the file already exists in the user's document folder, making sure to only copy the file from the bundle when required (otherwise you'll overwrite your modified file with the original bundle copy every time).
To escape from all the hassle with writing to a file in a specific directory, use the NSUserDefaults class to store/retrieve a key-value pair. That way you'd still have hair when you're 64.

What is the correct way to save user data using NSFileManager?

I am having trouble initializing dictionaries I use throughout my program to store user achievements and scores.
I have almost identical code for the two dictionaries and only the gameCenterData dictionary seems to be working properly. I have tried altering the plist file name and contents yet nothing seems to make the playerData dictionary properly load info from the file as it should
In the Root View Controller I have the following code (playerData and gameCenterData are both NSMutableDictionaries and the plist files are in the proper place)
-(NSString *)scoreFilePath
{
NSArray *scorePath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [scorePath objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"PlayerScoreData.plist"];
}
-(NSString *)gameCenterFilePath
{
NSArray *gameCenterPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [gameCenterPath objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"GameCenterData.plist"];
}
then the view did load
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *playerDataPath = [self scoreFilePath];
if (! [[NSFileManager defaultManager] fileExistsAtPath:playerDataPath])
{
playerData = [NSMutableDictionary dictionaryWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"scoreData.plist"]];
[playerData writeToFile:[self scoreFilePath] atomically:YES];
NSLog(#"Player data file does not exist");
}
else
{
playerData = [[NSMutableDictionary alloc] initWithContentsOfFile:[self scoreFilePath]];
NSLog(#"player data file exists");
}
NSLog(#"scoreData is %#",playerData);
NSString *gameCenterPath = [self gameCenterFilePath];
if (! [[NSFileManager defaultManager] fileExistsAtPath:gameCenterPath])
{
gameCenterData = [NSMutableDictionary dictionaryWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"gameCenterData.plist"]];
[gameCenterData writeToFile:[self gameCenterFilePath] atomically:YES];
NSLog(#"game center data file does not exist");
}
else
{
gameCenterData = [[NSMutableDictionary alloc] initWithContentsOfFile:[self gameCenterFilePath]];
NSLog(#"game center data file exists");
}
NSLog(#"gameCenterData is %#",gameCenterData);
the output is as follows
2012-08-05 11:46:49.991 GlobeRoller[6410:1be03] Player data file does not exist
2012-08-05 11:46:49.992 GlobeRoller[6410:1be03] playerData is (null)
2012-08-05 11:46:50.061 GlobeRoller[6410:1be03] game center data file does not exist
2012-08-05 11:46:50.062 GlobeRoller[6410:1be03] gameCenterData is {
"Career Odometer" = 0;
"Career Score" = 0;
"Cities Found" = 0;
"Offline Games Played" = 0;
"Online Games Played" = 0;
"Online Games Won" = 0;
}
I have searched all of the questions and answers to see if I can find out why this isn't working for both methods. Any help you could offer, or resources you could point me to I would greatly appreciate.
Thank you,
CF
The plist file you are trying to load from the bundle is either not there, or has been created improperly. Directly from the documentation of dictionaryWithContentsOfFile:.
Return Value
A new dictionary that contains the dictionary at path, or
nil if there is a file error or if the contents of the file are an
invalid representation of a dictionary.
You should make sure you are using the proper file name, and then open your plist in Xcode to see if it is properly formatted.
iOS is case sensitive. Are you sure that your file in the bundle is lower case, i.e. "#"scoreData.plist", and not upper case like the name your code uses? Also, verify that these two files are in your bundle - check the build phase or select the files (one at a time) and look in the 3rd Xcode pane in the file attribute section (to verify they are included in your target). If all that looks good then when you try to retrieve the files from your bundle:
Also, don't try to find the file at the root level of the bundle - you should be using:
NSString *path = [[NSBundle mainBundle] pathForResource:#"GameCenterData" ofType:#"plist"];
NSLog(#"PATH is %#", path);
...then use path instead of the code you are using now

is it possible to share images across NSBundles?

Apologies for the vague question title...
here is my scenario:
I am dynamically loading UIView content from nib files stored in NSBundles
BOOL bundlePathExists = [fm fileExistsAtPath:bundlePath];
if (bundlePathExists) {
NSBundle *tempBundle = [NSBundle bundleWithPath:bundlePath];
UIViewController *vc = [[UIViewController alloc] initWithNibName:nibName bundle:tempBundle];
[self.view addSubview:vc.view];
}
(note: the above is a simplified excerpt of the actual code, as not to confuse the nature of the question - the mechanics of doing this is relatively well documented here)
these nib files contain a number of images, some of which are unique to that bundle, and others that are shared across multiple bundles.
i'd like to be able to store the images that are common inside the main bundle, so they don't take up space in the other bundles, and to minimise the maintenance of the project as a whole - eg if i change one of those common images, i'd rather not have to rebuild every bundle it is referenced by.
i realize i could do this programatically by using
[commonImageViewIvar setImage:[UIImage imageWithName:commonImageName]];
however i would prefer to achieve this without writing custom code for each view (ie the view controller instantiated is not customised per nib, hence all information needs to be stored in the nib)
As promised here is the solution i came up with:
The bundle files themselves are downloaded as described here. To add to the information listed there, I found that if you create the views in an iphone app first, you can then preview them in the simulator. then, you can create a new project, as a bundle, and drag and drop ALL the image files & the xib &.h file into the new project. don't drag the .m file across as this creates build issues. then, ensure that the project settings define the BASE SDK as "Latest IOS", and not the default Mac OS X setting that would have been selected. you can then still edit the xib by double clicking on it. any common files can be deselected from the build by unticking the "target" column for that file. see "Additional Information" later in this answer post.
once i have downloaded each bundle in a zip file from the server, i utilize the following methods i coded for this purpose:
-(NSArray *) imageFileExtensions {
return [NSArray arrayWithObjects:#".png",#".gif",#".jpg",#".jpeg",#".bmp", nil];
}
and
-(void) cloneImageFilesInPath:(NSString *)path toPath:(NSString*)clonePath {
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *extensions = [self imageFileExtensions];
if ([fm fileExistsAtPath:path]) {
NSArray *files = [fm contentsOfDirectoryAtPath:path error:nil];
for (NSString *file in files){
for (NSString *ext in extensions) {
if ([file hasSuffix:ext]) {
NSString *targetFile = [clonePath stringByAppendingPathComponent:file];
if (![fm fileExistsAtPath:targetFile]) {
[fm createSymbolicLinkAtPath:targetFile withDestinationPath:[path stringByAppendingPathComponent:file] error:nil];
}
break;
}
}
}
}
}
these methods create scan the main app bundle directory and for each image file that is NOT in the custom bundle directory, a symbolic link is created inside the custom bundle directory to point back to the main app bundle directory.
They are invoked in my app delegate as follows:
...(insert code to unzip the file to bundlePath here)...
[self cloneImageFilesInPath:[[NSBundle mainBundle] bundlePath] toPath:bundlePath];
Additional information
to create the actual bundles, i made a separate app that removed any image files from a custom bundle directory, if those filenames are present in a directory that contains the common image files that are deployed in the main app bundle. i later discovered you can prevent xcode from including certain files from the build by deselecting the target column checkbox for that file, so this step is not necessarily needed - if you have a lot of views to create however, it may be easier to just leave them in the bundles, and strip them out using this method.
-(void) removeDuplicatedImageFilesInPath:(NSString *)sourcePath fromTargetPath:(NSString*)path {
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *extensions = [self imageFileExtensions];
if ([fm fileExistsAtPath:path]) {
NSArray *files = [fm contentsOfDirectoryAtPath:sourcePath error:nil];
for (NSString *file in files){
for (NSString *ext in extensions) {
if ([file hasSuffix:ext]) {
NSString *targetPath = [path stringByAppendingPathComponent:file];
if ([fm fileExistsAtPath:targetPath]) {
[fm removeItemAtPath:targetPath error:nil];
}
break;
}
}
}
}
}
Further Information for dealing with the zip files
I opted on using ObjectiveZip (following a suggestion by a poster here. To simplify the task, i wrapped this in the following 2 app delegate methods (one is used in the actual app, another in the the offline bundle creation app)
in the main app
-(void) unzipArchive:(NSString *)zipFileName toPath:(NSString *)path {
NSFileManager *fm = [NSFileManager defaultManager];
ZipFile *unzipFile = [[ZipFile alloc] initWithFileName:zipFileName mode:ZipFileModeUnzip];
NSArray *infos= [unzipFile listFileInZipInfos];
[unzipFile goToFirstFileInZip];
for (FileInZipInfo *info in infos) {
ZipReadStream *read1= [unzipFile readCurrentFileInZip];
NSMutableData *fileData = [[[NSMutableData alloc] initWithLength:info.length] autorelease];
int bytesRead1 = [read1 readDataWithBuffer:fileData];
if (bytesRead1 == info.length) {
NSString *fileName = [path stringByAppendingPathComponent:info.name ];
NSString *filePath = [fileName stringByDeletingLastPathComponent];
[fm createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
[fileData writeToFile:fileName atomically:YES];
}
[read1 finishedReading];
[unzipFile goToNextFileInZip];
}
[unzipFile close];
[unzipFile release];
}
and in the custom bundle creation offline app.
-(void) createArchive:(NSString *) zipFileName usingPath:(NSString *)path {
NSArray *files = [self filesInDirectory:path];
ZipFile *zipFile= [[ZipFile alloc] initWithFileName:zipFileName mode:ZipFileModeCreate];
for (NSString *file in files) {
NSString *fileNameForZip = [file substringFromIndex:path.length];
ZipWriteStream *stream= [zipFile writeFileInZipWithName:fileNameForZip fileDate:[NSDate dateWithTimeIntervalSinceNow:-86400.0] compressionLevel:ZipCompressionLevelBest];
[stream writeData:[NSData dataWithContentsOfFile:file]];
[stream finishedWriting];
}
[zipFile close];
[zipFile release];
}
note: the previous method relies on the following 2 methods, which create an NSArray containing the fully qualified path to all files in the given path (recursing into sub directories)
-(void)loadFilesInDirectory:(NSString *)path intoArray:(NSMutableArray *)files {
NSFileManager *fm = [NSFileManager defaultManager];
NSMutableArray *fileList = [NSMutableArray arrayWithArray:[fm contentsOfDirectoryAtPath:path error:nil]];
for (NSString *file in fileList) {
BOOL isDirectory = NO;
NSString *filePath = [path stringByAppendingPathComponent:file];
if ([fm fileExistsAtPath:filePath isDirectory:&isDirectory]) {
if (isDirectory) {
[self loadFilesInDirectory:filePath intoArray:files];
} else {
[files addObject:filePath];
};
};
}
}
-(NSArray *)filesInDirectory:(NSString *)path {
NSMutableArray *files = [NSMutableArray array];
[self loadFilesInDirectory:path intoArray:files];
return files;
}

Resources