writeToFile does not work after removeItemAtPath - objective-c

I have an iOS app that at times needs to store UIImage objects locally.
I am using [UIImagePNGRepresentation(image) writeToFile:full_path options:NSAtomicWrite error:nil]; to save the image and [file_manager removeItemAtPath:full_path error:NULL]; to delete the file.
This all works great, however, whenever I delete a file, should I decide to save a new file (which just so happens to have the same name as the old file), the save code doesn't work and returns the following error:
: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed
error = 2 (No such file or directory)
: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed
error = 2 (No such file or directory)
: ImageIO: PNG zlib error
So heres what I don't get, why can't I save a file with the same name as the old file, after I have deleted the old file?
The reason I ask this, is that, my app will save certain image files and then when they are no longer needed, my app will delete them. However, there are times when my app needs the image files again (could be a few hours after deletion or a few weeks). When this happens, my app will load the appropriate image data and then try to save it. And thats when the error occurs.
Whats going wrong here?
Thanks for your time, Dan.
UPDATE - Here are the methods I have setup to save/access and delete my image files
-(void)save_local_image:(UIImage *)image :(NSString *)file_name {
// Get the app documents directory link.
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
// Add the new file name to the link.
NSString *database_link = [[NSString alloc] initWithString:[documents stringByAppendingPathComponent:file_name]];
// Save the image data locally.
[UIImagePNGRepresentation(image) writeToFile:database_link options:NSAtomicWrite error:nil];
}
-(UIImage *)get_local_image:(NSString *)file_name {
// Create the return data.
UIImage *image_data = nil;
// Get the app documents directory.
NSArray *directory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] error:NULL];
// Only check for data if at least
// one file has been saved locally.
if ([directory count] > 0) {
// Loop through the different local files.
for (NSString *path in directory) {
// Get the full local file URL.
NSString *full_path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:path];
// Get the range of the file name.
NSRange range = [full_path rangeOfString:file_name];
// Get the image data if it exists.
if ((range.location != NSNotFound) || (range.length == [file_name length])) {
// Load the image file in.
image_data = [UIImage imageWithContentsOfFile:full_path];
break;
}
}
}
return image_data;
}
-(void)delete_local_image:(NSString *)file_name {
// Get the app documents directory.
NSArray *directory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] error:NULL];
// Only check for data if at least
// one file has been saved locally.
if ([directory count] > 0) {
// Loop through the different local files.
for (NSString *path in directory) {
// Get the full local file URL.
NSString *full_path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:path];
// Get the range of the file name.
NSRange range = [full_path rangeOfString:file_name];
// Delete the local image data if it exists.
if ((range.location != NSNotFound) || (range.length == [file_name length])) {
NSError *testError = nil;
// Delete the image file.
NSFileManager *file_manager = [[NSFileManager alloc] init];
BOOL success = [file_manager removeItemAtPath:full_path error:&testError];
NSLog(#"%d", success);
if (testError != nil) {
NSLog(#"%#", testError.localizedDescription);
}
break;
}
}
}
}

I am using this to save and read:
- (void) saveImageToFile:(NSString *) urlImg withNameNumber:(int)numberName andQuestionId:(int) questionID
{
NSString* documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString* foofile = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"gallery%d%d.jpg",numberName,questionID]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
if (!fileExists) {
NSData *tempImgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#",#"https://testurl/",urlImg]]];
NSString *filename = [NSString stringWithFormat:#"gallery%d%d.jpg",numberName,questionID];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:filename];
[tempImgData writeToFile:imagePath atomically:YES];
}
}
-(UIImage*) readImageFromFile:(int)numberName andQuestionId:(int) questionID
{
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/gallery%d%d.jpg",documentsDirectoryPath,numberName,questionID]];
}

Related

Unable to delete the contents of a Directory in my Home Directory using NSDirectoryEnumerator

I have been unable to delete the contents of a directory using NSDirectoryEnumerator.
Here is my method:
- (BOOL) deleteDirectoryContents:(NSString *)directoryPath
{
BOOL success = FALSE;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:directoryPath isDirectory:&isDir] && isDir)
{
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:directoryPath];
NSLog(#"dirEnum in deleteDirectoryContents is %#", dirEnum);
NSString *documentsName;
// NSLog(#"[dirEnum nextObject] is: %#", [dirEnum nextObject]);
while (documentsName = [dirEnum nextObject])
{
NSString *filePath = [directoryPath stringByAppendingString:documentsName];
NSLog(#"filePath is: %#", filePath);
BOOL isFileDeleted = [fileManager removeItemAtPath:filePath error:nil];
if(isFileDeleted == NO)
{
NSLog(#"All Contents not removed");
success = FALSE;
break;
}
success = TRUE;
}
if (success) NSLog(#"All Contents Removed");
}
return success;
}
And this is my code in my main program:
NSString *testDir = #"/Users/grinch/MyTestTemp";
//testDir = [NSHomeDirectory() stringByAppendingPathComponent: #"MyTestTemp"];
NSLog(#"testDir is: %#", testDir);
BOOL result = [self deleteDirectoryContents:testDir];
NSLog(#"result is: %d", result);
Here is my console output:
testDir is: /Users/grinch/MyTestTemp
dirEnum in deleteDirectoryContents is <NSAllDescendantPathsEnumerator: 0x60000008c300>
result is: 0
I also checked the value of [dirEnum nextObject] (by uncommenting the NSLog statement in my code). It returns null. And I never see the "filePath is" NSLog statement. So the inner while loop is NEVER executed.
And yes the directory (with files) does exist in my home directory. I created these files. I have permissions. I can easily delete the files in this directory using Finder
What am I missing?
P.S. I did some more testing. It looks like my simple program in Xcode does not have permissions to access files and folders in my home directory. Why? I have no idea.
Here is my additional test code:
NSError *errorMsg;
[[NSFileManager defaultManager] removeItemAtPath:#"/Users/grinch/myTestTemp/Hello.txt" error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - File delete errorMsg is: %#", errorMsg);
success = [[NSFileManager defaultManager] removeItemAtPath:testDir error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - Folder delete errorMsg is: %#", errorMsg);}
And here is my console output:
2022-09-10 09:56:48.958352-0400 NSAlert[97106:7511815] ERROR - File delete errorMsg is: Error Domain=NSCocoaErrorDomain Code=513 "“Hello.txt” couldn’t be removed because you don’t have permission to access it." UserInfo={NSFilePath=/Users/grinch/myTestTemp/Hello.txt, NSUserStringVariant=(
), NSUnderlyingError=0x6040006421f0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
2022-09-10 09:56:48.960560-0400 NSAlert[97106:7511815] ERROR - Folder delete errorMsg is: Error Domain=NSCocoaErrorDomain Code=513 "“MyTestTemp” couldn’t be removed because you don’t have permission to access it." UserInfo={NSFilePath=/Users/grinch/MyTestTemp, NSUserStringVariant=(
), NSUnderlyingError=0x60400045ff80 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
So my questions are:
Why didn't the directory enumerator work?
Why doesn't Xcode have the permissions to delete items in my home folder?
How can I give Xcode the the ability (or permissions) to delete items in my home directory?
I have found a bug in my deleteDirectoryContents: method above. And as a result, have answered most of my questions!
First, the statement NSString *filePath = [directoryPath stringByAppendingString:documentsName]; in the while loop will not work. One must insert a / before documentsname. So the line should be NSString *filePath = [directoryPath stringByAppendingFormat:#"/%#",documentsName]; OR even better use NSString *filePath = [directoryPath stringByAppendingPathComponent:documentsName];.
Now the method deleteDirectoryContents: will work as expected.
Here is the code to the fixed method:
- (BOOL) deleteDirectoryContents:(NSString *)directoryPath
{
BOOL success = FALSE;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:directoryPath isDirectory:&isDir] && isDir)
{
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:directoryPath];
NSLog(#"dirEnum in deleteDirectoryContents is %#", dirEnum);
NSString *documentsName;
// NSLog(#"[dirEnum nextObject] is: %#", [dirEnum nextObject]);
while (documentsName = [dirEnum nextObject])
{
//NSString *filePath = [directoryPath stringByAppendingFormat:#"/%#",documentsName];
NSString *filePath = [directoryPath stringByAppendingPathComponent:documentsName];
NSLog(#"filePath is: %#", filePath);
NSError *errorMsg = nil;
BOOL isFileDeleted = [fileManager removeItemAtPath:filePath error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - Failed to delete file or folder at: %#. Error message is: %#", filePath, errorMsg);
if(isFileDeleted == NO)
{
NSLog(#"All Contents not removed");
success = FALSE;
break;
}
success = TRUE;
}
if (success) NSLog(#"All Contents Removed");
}
return success;
}
And I can confirm that if your program creates the directory and the files within it, the program can also delete these files using deleteDirectoryContents: while running in the Xcode sandbox DEPENDING on where the folder was originally created.
If the program creates the folder and files in a temporary directory (e.g. caches directory) NOT the users home folder, it works.
But due to Xcode sandboxing,
deleteDirectoryContents: does not appear to work on folder and files created by the program in the users Home directory when running within Xcode.
P.S. I used the following to obtain a temporary directory name:
- (NSString *) get_temporary_dir
{
NSString *path = nil;
NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if ([paths count]) {
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:bundleName];
} else {
path = NSTemporaryDirectory();
path = [path stringByAppendingPathComponent:bundleName];
}
return [[[NSString alloc] initWithUTF8String:[path UTF8String]] autorelease];
}
Then the program created the temporary directory using that name. The program created some files in that temporary directory and then was able to delete them using the deleteDirectoryContents: method above.

iCloud Drive - Read & Write (NSData)

Trying to save custom objects as NSData then upload to iCloud Drive
any direction?
I tried already the Key-Value Method, but it is limited for 1MB :/
is it the correct way?
if yes, how can I read from the iCloud path?
+(void)iCloudWrite:(NSData*)data
{
NSURL* ubiq = [[NSFileManager defaultManager]URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:#"iCloudHistory.zip"];
MyDocument *mydoc = [[MyDocument alloc] initWithFileURL:ubiquitousPackage];
mydoc.dataContent = data;
[mydoc saveToURL:[mydoc fileURL] forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success)
{
if (success)
{
NSLog(#"Synced with icloud");
}
else
NSLog(#"Syncing FAILED with icloud");
}];
}
EDITED:
// 1. Any file can be uploaded to iCloud container of any size (yes you should be having that much of space in iCloud) lets take an example SampleData.zip
// 2. This method will upload or sync SampleData.zip file in iCloud container, iCloud actually checks the metadata of your file before it uploads it into your iCloud container (so for first time it will upload the file and from next time it will only upload the changes)
-(void) iCloudSyncing:(id)sender
{
//Doc dir
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"SampleData.zip"];
NSURL *u = [[NSURL alloc] initFileURLWithPath:filePath];
NSData *data = [[NSData alloc] initWithContentsOfURL:u];
//Get iCloud container URL
NSURL *ubiq = [[NSFileManager defaultManager]URLForUbiquityContainerIdentifier:nil];// in place of nil you can add your container name
//Create Document dir in iCloud container and upload/sync SampleData.zip
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"]URLByAppendingPathComponent:#"SampleData.zip"];
Mydoc = [[MyDocument alloc] initWithFileURL:ubiquitousPackage];
Mydoc.zipDataContent = data;
[Mydoc saveToURL:[Mydoc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
if (success)
{
NSLog(#"SampleData.zip: Synced with icloud");
}
else
NSLog(#"SampleData.zip: Syncing FAILED with icloud");
}];
}
// 3 Download data from the iCloud Container
- (IBAction)GetData:(id)sender {
//--------------------------Get data back from iCloud -----------------------------//
id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (token == nil)
{
NSLog(#"ICloud Is not LogIn");
}
else
{
NSLog(#"ICloud Is LogIn");
NSError *error = nil;
NSURL *ubiq = [[NSFileManager defaultManager]URLForUbiquityContainerIdentifier:nil];// in place of nil you can add your container name
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"]URLByAppendingPathComponent:#"SampleData.zip"];
BOOL isFileDounloaded = [[NSFileManager defaultManager]startDownloadingUbiquitousItemAtURL:ubiquitousPackage error:&error];
if (isFileDounloaded) {
NSLog(#"%d",isFileDounloaded);
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//changing the file name as SampleData.zip is already present in doc directory which we have used for upload
NSString* fileName = [NSString stringWithFormat:#"RecSampleData.zip"];
NSString* fileAtPath = [documentsDirectory stringByAppendingPathComponent:fileName];
NSData *dataFile = [NSData dataWithContentsOfURL:ubiquitousPackage];
BOOL fileStatus = [dataFile writeToFile:fileAtPath atomically:NO];
if (fileStatus) {
NSLog(#"success");
}
}
else{
NSLog(#"%d",isFileDounloaded);
}
}
}
//4 voila its done :)
Don't forget to add following parameters to your plist
<key>NSUbiquitousContainers</key>
<dict>
<key>YourCloudContainerID</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>YourCloudContainerName</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>

How to zip folders in Ipad

In my Application,I am taking screenshots of image View and then I am saving those screen shots in document folder of the application.Now I want to Email all those images with the same folder structure they are in.Zipping all the folders containing the images?
-(void)ZipFile2{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [paths objectAtIndex:0];
BOOL isDir=NO;
NSArray *subpaths;
NSString *exportPath = docDirectory;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:exportPath isDirectory:&isDir] && isDir){
subpaths = [fileManager subpathsAtPath:exportPath];
}
NSString *archivePath = [docDirectory stringByAppendingString:#"Uploads.zip"];
ZipArchive *archiver = [[ZipArchive alloc] init];
[archiver CreateZipFile2:archivePath];
for(NSString *path in subpaths)
{
NSString *longPath = [exportPath stringByAppendingPathComponent:path];
if([fileManager fileExistsAtPath:longPath isDirectory:&isDir] && !isDir)
{
[archiver addFileToZip:longPath newname:path];
}
}
BOOL successCompressing = [archiver CloseZipFile2];
if(successCompressing)
NSLog(#"Success");
else
NSLog(#"Fail");
}
I use this code but the zip file is not created in my Ipad ??? can you Help Me
ZipArchive has a delegate protocol which sends a message whenever a error occurs.
add below ZipArchive allocation.
archiver.delegate = self
Add protocol to header class and then implement the following method:
- (void)ErrorMessage:(NSString*) msg {
NSLog(#"Zip error: %#", msg);
}

iOS list existing directories

I already get path for documents directory and create some directories inside. I already know how to check if directory exist, delete it or its files but, how could I list directories? Thank you.
for file listing I use:
int Count;
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"SomeDirectoryName"];
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
for (Count = 0; Count < (int)[directoryContent count]; Count++)
{
NSLog(#"File %d: %#", (Count + 1), [directoryContent objectAtIndex:Count]);
}
For example, this method removes all files from temporary directory of application:
- (void)cleatTmpDirectory
{
// Create a local file manager instance
NSFileManager *localFileManager = [[NSFileManager alloc] init];
NSURL *directoryToScan = [NSURL fileURLWithPath:[self applicationTmpDirectory]];
NSDirectoryEnumerator *dirEnumerator =
[[localFileManager enumeratorAtURL:directoryToScan
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLIsDirectoryKey,nil]
options: NSDirectoryEnumerationSkipsHiddenFiles |
NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsPackageDescendants
errorHandler:nil] retain];
NSError *error;
// Enumerate the dirEnumerator results, each value is stored in allURLs
for (NSURL *theURL in dirEnumerator)
{
// Retrieve whether a directory.
NSNumber *isDirectory;
[theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if ([isDirectory boolValue] == NO)
{
[localFileManager removeItemAtURL:theURL error:&error];
}
}
// Release the localFileManager.
[localFileManager release];
}
As you can find you should use NSDirectoryEnumerator *dirEnumerator and pass to its initialization method appropriate keys that you will then use.
Use the NSDirectoryEnumerator returned by NSFileManager's -enumeratorAtPath: method.

Appending NSString while using NSApplicationSupportDirectory to create a new directory

I have been trying to create a new file inside of my application support folder while using NSApplicationSupportDirectory; I can write a file to it, but I have been unable to create a folder inside of Application Support.
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *applicationDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the application support: (attempting to create the blasted directory inside of application support directory
NSString *fileName = [NSString stringWithFormat:#"%#/managersemail.txt",
applicationDirectory];
//create content - formats with the managersemail.txt location
NSString* content = [NSString stringWithFormat:#"%#",[nameField stringValue]];
//save content to the documents directory
[content writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSDictionary* errorDict;
The code that I have listed above works great, except for the part about creating the folder in which I want to place the managersemail.txt. I tried to mimic the stringWithFormat that is listed in the NSString* content and then varying several ways however to no avail! Any thoughts?
NSAppleEventDescriptor* returnDescriptor = NULL;
Perhaps the solution provided on Cocoa with Love might be useful?
Excerpt:
- (NSString *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory
inDomain:(NSSearchPathDomainMask)domainMask
appendPathComponent:(NSString *)appendComponent
error:(NSError **)errorOut
{
// Search for the path
NSArray* paths = NSSearchPathForDirectoriesInDomains(
searchPathDirectory,
domainMask,
YES);
if ([paths count] == 0)
{
// *** creation and return of error object omitted for space
return nil;
}
// Normally only need the first path
NSString *resolvedPath = [paths objectAtIndex:0];
if (appendComponent)
{
resolvedPath = [resolvedPath
stringByAppendingPathComponent:appendComponent];
}
// Check if the path exists
BOOL exists;
BOOL isDirectory;
exists = [self
fileExistsAtPath:resolvedPath
isDirectory:&isDirectory];
if (!exists || !isDirectory)
{
if (exists)
{
// *** creation and return of error object omitted for space
return nil;
}
// Create the path if it doesn't exist
NSError *error;
BOOL success = [self
createDirectoryAtPath:resolvedPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (!success)
{
if (errorOut)
{
*errorOut = error;
}
return nil;
}
}
if (errorOut)
{
*errorOut = nil;
}
return resolvedPath;
}
Maybe you can try using the NSFileManager to create the folder, then write the file into the folder.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *applicationSupport = [[NSString stringWithString:#"~/Library/Application Support/'YOUR APP'] stringByExpandingTildeInPath];
if ([fileManager fileExistsAtPath:applicationSupport] == NO)
[fileManager createDirectoryAtPath:applicationSupport withIntermediateDirectories:YES attributes:nil error:nil];
NSString *fileName = [NSString stringWithFormat:#"%#/managersemail.txt", applicationSupport];
NSString* content = [NSString stringWithFormat:#"%#",[nameField stringValue]];
//save content to the documents directory
[content writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
So something like that should work. Feel free to leave comments to ask questions.