I'm using a UIDocument with iCloud. I'm not using CoreData. What's the best way to delete a UIDocument?
Copied from the "Deleting a Document" section of the Document-Based App Programming Guide for iOS.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fileCoordinator coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForDeleting
error:nil byAccessor:^(NSURL* writingURL) {
NSFileManager* fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtURL:writingURL error:nil];
}];
});
N.B.: "When you delete a document from storage, your code should approximate what UIDocument does for reading and writing operations. It should perform the deletion asynchronously on a background queue, and it should use file coordination."
To delete the document from iCloud, first you have to get the filename you want to delete. and then you can delete it using NSFileManager.
NSString *saveFileName = #"Report.pdf";
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:saveFileName];
NSFileManager *filemgr = [NSFileManager defaultManager];
[filemgr removeItemAtURL:ubiquitousPackage error:nil];
This is the way, which i used to delete document, Check it out.
It is woking great for me.
Thanks
SWIFT 3 come from #AlexChaffee 's answer
func deleteZipFile(with filePath: String) {
DispatchQueue.global(qos: .default).async {
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinate(writingItemAt: URL(fileURLWithPath: filePath), options: NSFileCoordinator.WritingOptions.forDeleting, error: nil) {
writingURL in
do {
try FileManager.default.removeItem(at: writingURL)
} catch {
DLog("error: \(error.localizedDescription)")
}
}
}
}
I think I found a solution:
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:url destinationURL:nil error:nil]
Source:
http://oleb.net/blog/2011/11/ios5-tech-talk-michael-jurewitz-on-icloud-storage/
See Apple documentation of "Managing the Life-Cyle of a Document" under 'Deleting a Document."
Related
Where does startDownloadingUbiquitousItemAtURL:error method save downloaded file in local?
Is it the same as given URL?
Yes , its the same URL
The file in place before it downloads from iCloud to the device is a placeholder of sorts.
You can check the status of the URL with the key NSURLUbiquitousItemIsDownloadedKey
NSURL *foo = file://cloud/container/reference/tofile;
NSNumber *isDownloadedValue = NULL;
BOOL success = [foo getResourceValue:&isDownloadedValue forKey: NSURLUbiquitousItemIsDownloadedKey error:NULL];
if (success && ![isDownloadedValue boolValue]) {
[[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:foo error:NULL];
}
Theres no error handling in that snippet which you will want to do in production code.
I am downloading images to my app which after a few weeks the user will not care about. I download them to the app so they will not have to be downloaded every launch. The problem is I do not want the Documents folder to get bigger than it has to over time. So I thought I could "clean up" file older than a Month.
The problem is, there will be a few files in there that WILL be older than a month but that I do NOT want to delete. They will be Static Named files so they will be easy to identify and there will only be 3 or 4 of them. While there might be a few dozen old files I want to delete. So heres an example:
picture.jpg <--Older than a month DELETE
picture2.jpg <--NOT older than a month Do Not Delete
picture3.jpg <--Older than a month DELETE
picture4.jpg <--Older than a month DELETE
keepAtAllTimes.jpg <--Do not delete no matter how old
keepAtAllTimes2.jpg <--Do not delete no matter how old
keepAtAllTimes3.jpg <--Do not delete no matter how old
How could I selectively delete these files?
Thanks in advance!
Code to delete files which are older than two days. Originally I answered here. I tested it and it was working in my project.
P.S. Be cautious before you delete all files in Document directory because doing so you might end up losing your Database file(If you are using..!!) there which may cause trouble for your Application. Thats why I have kept if condition there. :-))
// Code to delete images older than two days.
#define kDOCSFOLDER [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"]
NSFileManager* fileManager = [[[NSFileManager alloc] init] autorelease];
NSDirectoryEnumerator* en = [fileManager enumeratorAtPath:kDOCSFOLDER];
NSString* file;
while (file = [en nextObject])
{
NSLog(#"File To Delete : %#",file);
NSError *error= nil;
NSString *filepath=[NSString stringWithFormat:[kDOCSFOLDER stringByAppendingString:#"/%#"],file];
NSDate *creationDate =[[fileManager attributesOfItemAtPath:filepath error:nil] fileCreationDate];
NSDate *d =[[NSDate date] dateByAddingTimeInterval:-1*24*60*60];
NSDateFormatter *df=[[NSDateFormatter alloc]init];// = [NSDateFormatter initWithDateFormat:#"yyyy-MM-dd"];
[df setDateFormat:#"EEEE d"];
NSString *createdDate = [df stringFromDate:creationDate];
NSString *twoDaysOld = [df stringFromDate:d];
NSLog(#"create Date----->%#, two days before date ----> %#", createdDate, twoDaysOld);
// if ([[dictAtt valueForKey:NSFileCreationDate] compare:d] == NSOrderedAscending)
if ([creationDate compare:d] == NSOrderedAscending)
{
if([file isEqualToString:#"RDRProject.sqlite"])
{
NSLog(#"Imp Do not delete");
}
else
{
[[NSFileManager defaultManager] removeItemAtPath:[kDOCSFOLDER stringByAppendingPathComponent:file] error:&error];
}
}
}
You can get the file creation date, look at this SO Post and then just compare the dates. and create two different arrays for files needs to be deleted and non to be deleted..
My two cents worth. Change meetsRequirement to suit.
func cleanUp() {
let maximumDays = 10.0
let minimumDate = Date().addingTimeInterval(-maximumDays*24*60*60)
func meetsRequirement(date: Date) -> Bool { return date < minimumDate }
func meetsRequirement(name: String) -> Bool { return name.hasPrefix(applicationName) && name.hasSuffix("log") }
do {
let manager = FileManager.default
let documentDirUrl = try manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
if manager.changeCurrentDirectoryPath(documentDirUrl.path) {
for file in try manager.contentsOfDirectory(atPath: ".") {
let creationDate = try manager.attributesOfItem(atPath: file)[FileAttributeKey.creationDate] as! Date
if meetsRequirement(name: file) && meetsRequirement(date: creationDate) {
try manager.removeItem(atPath: file)
}
}
}
}
catch {
print("Cannot cleanup the old files: \(error)")
}
}
To find creation date of your file, you can refer to a very useful StackOverflow post:
iOS: How do you find the creation date of a file?
Refer to this post, this may help you in deleting them. You can just get a rough idea on what needs to be done for deletion of those data from Documents Directory:
How to delete files from iPhone's document directory which are older more than two days
Hope this helps you.
Here's a function that doesn't use string comparison for the dates and prefetches the modification time in the enumerator:
+ (NSArray<NSURL *> *)deleteFilesOlderThan:(NSDate *)earliestDateAllowed
inDirectory:(NSURL *)directory {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator<NSURL *> *enumerator =
[fileManager enumeratorAtURL:directory
includingPropertiesForKeys:#[ NSURLContentModificationDateKey ]
options:0
errorHandler:^BOOL(NSURL *_Nonnull url, NSError *_Nonnull error) {
NSLog(#"Failed while enumerating directory '%#' for files to "
#"delete: %# (failed on file '%#')",
directory.path, error.localizedDescription, url.path);
return YES;
}];
NSURL *file;
NSError *error;
NSMutableArray<NSURL *> *filesDeleted = [NSMutableArray new];
while (file = [enumerator nextObject]) {
NSDate *mtime;
if (![file getResourceValue:&mtime forKey:NSURLContentModificationDateKey error:&error]) {
NSLog(#"Couldn't fetch mtime for file '%#': %#", file.path, error);
continue;
}
if ([earliestDateAllowed earlierDate:mtime] == earliestDateAllowed) {
continue;
}
if (![fileManager removeItemAtURL:file error:&error]) {
NSLog(#"Couldn't delete file '%#': %#", file.path, error.localizedDescription);
continue;
}
[filesDeleted addObject:file];
}
return filesDeleted;
}
If you don't care about the files that got deleted you could make it return BOOL to indicate whether there were any errors, or simply void if you just want to make a best-effort attempt.
To selectively keep some of the files, either add a regular expression argument to the function that should match files to keep, and add a check for that in the while loop (seems to fit your use case best), or if there's a discrete amount of files with different patterns you could accept a NSSet with the filenames to keep and check for inclusion in the set before proceeding to the delete.
Also just mentioning this here since it might be relevant for some: The file system on iOS and OSX doesn't store mtime with greater precision than a second, so watch out for that if you require millisecond-precision or similar.
Corresponding test case to drop into your test suite if you want:
#interface MCLDirectoryUtilsTest : XCTestCase
#property NSURL *directory;
#end
#implementation MCLDirectoryUtilsTest
- (void)setUp {
NSURL *tempdir = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
self.directory = [tempdir URLByAppendingPathComponent:[NSUUID UUID].UUIDString isDirectory:YES];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createDirectoryAtURL:self.directory
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
- (void)tearDown {
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtURL:self.directory error:nil];
}
- (void)testDeleteFilesOlderThan {
NSFileManager *fileManager = [NSFileManager defaultManager];
// Create one old and one new file
[fileManager createFileAtPath:[self.directory URLByAppendingPathComponent:#"oldfile"].path
contents:[NSData new]
attributes:#{
NSFileModificationDate : [[NSDate new] dateByAddingTimeInterval:-120],
}];
[fileManager createFileAtPath:[self.directory URLByAppendingPathComponent:#"newfile"].path
contents:[NSData new]
attributes:nil];
NSArray<NSURL *> *filesDeleted =
[MCLUtils deleteFilesOlderThan:[[NSDate new] dateByAddingTimeInterval:-60]
inDirectory:self.directory];
XCTAssertEqual(filesDeleted.count, 1);
XCTAssertEqualObjects(filesDeleted[0].lastPathComponent, #"oldfile");
NSArray<NSString *> *contentsInDirectory =
[fileManager contentsOfDirectoryAtPath:self.directory.path error:nil];
XCTAssertEqual(contentsInDirectory.count, 1);
XCTAssertEqualObjects(contentsInDirectory[0], #"newfile");
}
In Swift 3 and 4, to delete a specific file in DocumentsDirectory
do{
try FileManager.default.removeItem(atPath: theFile)
} catch let theError as Error{
print("file not found \(theError)")
}
I am having our app move it's readonly db to the App Support directory so that content updates can update it as well(over the wire updates, not app updates). The code below is in the app delegate to copy over the db, however on the first run NSFileManager in following attempts(during that run) to see if it is there or to load it does not see the copied file. However, it is copying it because if I close the app and restart it, everything works fine. I'm at a lose.
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *err = nil;
NSURL *ASD = [fm URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&err];
if (!err) {
NSURL* path = [NSURL URLWithString:DATABASE_NAME relativeToURL:ASD];
NSString *bundle = [[ NSBundle mainBundle] pathForResource:#"datafiles/data_main" ofType:#"sqlite"];
if ([fm fileExistsAtPath:[path path]]) {
if([DatabaseManager isBundledDBNewerThenInUse]){
NSLog(#"bundled db is newer");
[DatabaseManager close];
[fm removeItemAtURL:path error:&err];
if (err) {
NSLog(#"Error deleting old DB:%#",err);
}
else {
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
{
NSLog(#"db should have been copied over correctly");
}
}
}
}
else{
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
}
}
else
{
NSLog(#"Error in opening AS for DB copy:%#",err);
}
"[DatabaseManager isBundledDBNewerThenInUse]" returns YES if the db in the App Support directory either isn't there or has a version that is older then the one in the bundle. It opens the db in the App Support directory thus the [DatabaseManager close] before trying to remove it. I'm using FMDB in my DatabaseManager if that helps at all. But as I said after that initial load if you kill the app and go back into it it work perfectly. (On a database update from bundle you the db isn't updated on that first load either.) Any help would be great and if you need more info please just ask! Thanks!
From the bahaviour you describe (i.e. you restart the app, then it finds the copied database) it sounds a bit like you're maybe missing a crucial database open/close call somewhere.
I'd double-check where you make any calls to database open and close, and make sure it makes sense. Are you missing a database open call? Should there be a database open call in the above code, in the cases where the database gets copied from app bundle to the app support directory, after the copy has happened?
The answer was dispatch_async on the main thread and then double checking the cached data and reloading it if needed. Corrected code:
dispatch_async(dispatch_get_main_queue(),^(){
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *err = nil;
NSURL *ASD = [fm URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&err];
if (!err) {
NSURL* path = [NSURL URLWithString:DATABASE_NAME relativeToURL:ASD];
NSString *bundle = [[ NSBundle mainBundle] pathForResource:#"datafiles/data_main" ofType:#"sqlite"];
if ([fm fileExistsAtPath:[path path]]) {
if([DatabaseManager isBundledDBNewerThenInUse]){
NSLog(#"bundled db is newer");
[DatabaseManager close];
[fm removeItemAtURL:path error:&err];
if (err) {
NSLog(#"Error deleting old DB:%#",err);
}
else {
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
{
NSLog(#"db should have been copied over correctly");
}
}
}
}
else{
[fm copyItemAtPath:bundle toPath:[path path] error:&err];
if (err) {
NSLog(#"Error in copying over DB:%#",err);
}
else
NSLog(#"DB Copied");
}
}
else
{
NSLog(#"Error in opening AS for DB copy:%#",err);
}
});
I want the user to select any file present in her/his iPhone so that it’s used as an e-mail attachment. For this purpose, I want to show the list of all files and folders present in iPhone. But how would I get the list of those files? Is there any way to get that list?
Take into account that your app runs in a sandbox and would not be able to get any folder/file outside of that sandbox.
ObjectiveC
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *fileList = [manager contentsOfDirectoryAtPath:documentsDirectory error:nil];
for (NSString *s in fileList){
NSLog(#"%#", s);
}
Swift 4
guard let documentsDirectory = try? FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else { return }
guard let fileEnumerator = FileManager.default.enumerator(at: documentsDirectory, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions()) else { return }
while let file = fileEnumerator.nextObject() {
print(file)
}
Here's the slowest(?) approach:
NSFileManager * fileManager = [NSFileManager new];
NSArray * subpaths = [fileManager subpathsAtPath:path];
but that should at least point you to a more specialized implementation for your needs.
Slightly lower level abstractions which allow you to enumerate on demand include NSDirectoryEnumerator and CFURLEnumerator. Depending on the depth of the directory, these have the potential to save much unnecessary interactions with the filesystem, compared to -[NSFileManager subpathsAtPath:].
You can use NSDirectoryEnumerator via NSFileManager.enumeratorAtPath
From the docs:
NSString *docsDir = [NSHomeDirectory() stringByAppendingPathComponent: #"Documents"];
NSFileManager *localFileManager=[[NSFileManager alloc] init];
NSDirectoryEnumerator *dirEnum =
[localFileManager enumeratorAtPath:docsDir];
NSString *file;
while ((file = [dirEnum nextObject])) {
if ([[file pathExtension] isEqualToString: #"doc"]) {
// process the document
[self scanDocument: [docsDir stringByAppendingPathComponent:file]];
}
}
swift 3
let fileManager:FileManager = FileManager()
let files = fileManager.enumerator(atPath: NSHomeDirectory())
while let file = files?.nextObject() {
print("Files::",file)
}
I'm an author of FileExplorer control which is a file browser for iOS and fulfills most of your requirements. Note that it allows you to browse only those files and directories that are placed inside your sandbox.
Here are some of the features of my control:
Possibility to choose files or/and directories if there is a need for that
Possiblity to remove files or/and directories if there is a need for that
Built-in search functionality
View Audio, Video, Image and PDF files.
Possibility to add support for any file type.
You can find my control here.
i have a program which i can download a video file from a server to the app's directory but how do i retrieve the information about this file (mp4). I would like know the size of that file and it there a way to know when the file is being created as in a date or time? Thanks in advance
NSError *error = nil;
NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath: filePath error: &error];
if ( fileInfo ) {
// examine fileInfo, see NSDictionary (NSFileAttributes) in NSFileManager.h
} else {
// handle the error
}