Check if file deletion was successful - ios

According to XCode documentation (alt-click), removeItemAtPath returns true or false. The below code however gives me the following error:
Cannot convert value of type '()' to specified type 'Bool'.
let result: Bool = try NSFileManager.defaultManager().removeItemAtPath(<my file path here>)
Is the documentation wrong? How do I check for successful deletion of the file? Will the execution of following code be skipped if the error is thrown in removeItemAtPath?
Example:
try NSFileManager.defaultManager().removeItemAtPath(<my file path here>)
doOtherStuff()
Will doOtherStuff be called if an error was thrown?

Will doOtherStuff be called if an error was thrown?
No. The whole point of try is that if it fails it exits immediately from the current scope. That is why you don't have to capture and test the result and/or an NSError pointer (and cannot do so).

Per the comments, you want to use a Do/Try/Catch block.
do {
try NSFileManager.defaultManager().removeItemAtPath("<my file path here>")
} catch {
print ("The file could not be removed")
}
If the file is removed, the code in the try block will be executed. If the file is not removed, the code in the catch block is executed.
For example, if you put print ("Success") in your try block, that print statement will execute if the file is successfully removed.
Likewise in the catch block, you can put whatever code you want to execute if the file is not removed. I put a simple print statement but you can put whatever you want.

here is a method that i use with try/catch:
func deleteFileFromDocumentsDirectory(fileName : String) -> () {
// Optional 1: split file by dot "."
let fullName = fileName.componentsSeparatedByString(".")
let fileName = fullName[0];
let fileExtension = fullName[1];
let documentsFolder : String = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.UserDomainMask, true)[0]
let fileManager = NSFileManager.defaultManager()
let destinationPath = documentsFolder + "/" + fileName + "." + fileExtension
// Optional 2: check, if file exits
let fileExists = fileManager.fileExistsAtPath(destinationPath)
if fileExists {
do {
try fileManager.removeItemAtPath(destinationPath)
} catch let error as NSError {
print("Could not delete \(error), \(error.userInfo)")
}
}
}

Related

How do I write a file in iOS using FileManager?

I have the following code with the results of the print statements in comments beside the statements:
let myImageData = FileManager.default.contents(atPath: myURL.absoluteString)
var mySaveToURL: URL = FileManager.default.url(forUbiquityContainerIdentifier: nil)!
mySaveToURL.appendPathComponent(myURL.pathComponents.last!)
print("mySaveToURL=", mySaveToURL) // mySaveToURL= file:///Users/shinehah/Library/Developer/CoreSimulator/Devices/693D4940-1B91-43E1-B5AD-88E9763046C7/data/Library/Mobile%20Documents/iCloud~us~gnolaum~TrialNotifications/ABF236AE-A6E7-403E-ADC4-5BAA5DC734B3.jpeg
let resultCreateFile = FileManager.default.createFile(atPath: mySaveToURL.absoluteString, contents: myImageData, attributes: nil)
print("resultCreateFile=", resultCreateFile) // resultCreateFile= false
do {
try FileManager.default.copyItem(at: myURL, to: mySaveToURL)
print("copy success!") // copy success!
} catch {
print(error.localizedDescription)
}
As you can see I am not able to successfully execute the createFile() method of FileManager but was able to successfully execute the copyItem() method to the same URL.
What do I check to be able to figure out how to get the createFile() method to work?
The error occurs because you are using the wrong API. To get the path of a file system URL you have to use path.
let resultCreateFile = FileManager.default.createFile(atPath: mySaveToURL.path, contents: myImageData, attributes: nil)
However there is no reason to create the file explicitly. Just copy the other file.

Swift 3.0 FileManager.fileExists(atPath:) always return false

When I use method .fileExists(atPath:)to judge whether the file is exist in file system, the method always return false to me. I checked the file system and the file do exist. Here is my code:
let filePath = url?.path
var isDir : ObjCBool = false
if(self.fileManager.fileExists(atPath: filePath!, isDirectory: &isDir)){
let result = NSData(contentsOfFile: filePath!)
}
or
let filePath = url?.path
if(self.fileManager.fileExists(atPath: filePath!)){
let result = NSData(contentsOfFile: filePath!)
}
the if clause will always be skipped.
I assume your url is an URL type. If so try this out:
let filePath = url?.path // always try to work with URL when accessing Files
if(FileManager.default.fileExists(atPath: filePath!)){ // just use String when you have to check for existence of your file
let result = NSData(contentsOf: url!) // use URL instead of String
}
Saying enough, you should change your implementation like this:
if(FileManager.default.fileExists(atPath: (url?.path)!)){ // just use String when you have to check for existence of your file
let result = NSData(contentsOf: url!) // use URL instead of String
}
EDIT: 1
There is even more better way, you can call it swift-way (:D). You don't have to explicitly check for file existence.
guard let result = NSData(contentsOf: fileURL) else {
// No data in your fileURL. So no data is received. Do your task if you got no data
// Keep in mind that you don't have access to your result here.
// You can return from here.
return
}
// You got your data successfully that was in your fileURL location. Do your task with your result.
// You can have access to your result variable here. You can do further with result constant.
print(result)
Update for Swift 3.0+ without the Objective-Cish NS prefix:
do {
let result = try Data(contentsOf: fileURL)
print(result)
} catch {
print(error)
}
in swift 3
just in case anyone gets confused like i did, here's the full snippets:
let str = "file:///Users/martian2049/Library/Developer/CoreSimulator/Devices/67D744AA-6EEC-4AFD-A840-366F4D78A18C/data/Containers/Data/Application/DD96F423-AF9F-4F4D-B370-94ADE7D6D0A5/Documents/72b8b0fb-7f71-7f31-ac9b-f9cc95dfe90d.mp3"
let url = URL(string: str)
print(url!.path,"\n")
if FileManager.default.fileExists(atPath: url!.path) {
print("FILE Yes AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
this prints
/Users/martian2049/Library/Developer/CoreSimulator/Devices/67D744AA-6EEC-4AFD-A840-366F4D78A18C/data/Containers/Data/Application/DD96F423-AF9F-4F4D-B370-94ADE7D6D0A5/Documents/72b8b0fb-7f71-7f31-ac9b-f9cc95dfe90d.mp3
FILE Yes AVAILABLE
notice how the 'file://' got chopped off?
I want to share my experience, in case anyone else gets baffled by this.
Tested on iOS 10-11, Xcode 9.2 and Swift 3.2.
Short answer: if you save a file path to disk, you may solve by not including the Documents directory in it.
Instead, every time you need to retrieve the file with the saved path, get the Documents directory and append the path.
For an iOS app, I was saving an image to .../Documents/Pictures through the relative URL, let's say url.
As the image was saved, a path, let's say url.path, was saved too in a Core Data entity.
When I later tried retrieving the image through FileManager.default.fileExists(atPath: url.path), it always returned false.
I was testing the app on my iPhone. It turned out that, for some reason, every time I ran the app from Xcode, the app identifier folder changed!!
So:
App opened from Xcode -> Image saved -> app closed -> app opened from physical device ->
fileExists -> TRUE
App opened from Xcode -> Image saved -> app closed -> app opened from Xcode -> fileExists -> FALSE
You can check if this is your case by getting and printing the Document folder path (or URL, it doesn't matter) and comparing it with the saved path (or URL). If you get something like this:
/var/mobile/Containers/Data/Application/5D4632AE-C432-4D37-A3F7-ECD05716AD8A/Documents..
/var/mobile/Containers/Data/Application/D09904C3-D80D-48EB-ACFB-1E42D878AFA4/Documents..
you found the issue.
Just use path instead of absoluteString to remove file://
FileManager.default.fileExists(atPath: URL.init(string: "your_url")!.path)
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true);
var path = paths[0] as String;
path = path + "/YourFilePath"
if((NSFileManager.defaultManager().fileExistsAtPath(path))) {
let result = NSData(contentsOfFile: filePath!)}
Try the above code and check again
I had the same problem this worked for me
filePath.replacingOccurrences(of: "file://", with: "")
First, what does your file path looks like? If the path begins with a ~,then it must be expanded with expandingTildeInPath;
Check if the path is inaccessible to your app. iOS App can only visits its sandbox directories.

How to handle errors with Swift (FileManager and others in general) [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
Note: I posted a lazy question before for converting code to Swift 3 (deleted it)
Apple has some sample code for managing files. It is an old guide and is all in Objective-C. I converted the snippet to Swift 3. My concern is over the error handling part. I'm nesting multiple do/catch blocks... just want to know if this is the optimal way of doing things??
There is a similar question/amswer to this here.
The document is: Apple File System Programming Guide, under section "Managing Files and Directories".
This is my code (converted to Swift 3):
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch let error {
// If the operation failed again, abort for real.
print("Operation failed again, abort with error: \(error)")
}
} catch let error {
// If the operation failed again, abort for real.
print("Other error: \(error)")
}
}
}
This is Apple's code in their docs which I converted:
- (void)backupMyApplicationData {
// Get the application's main data directory
NSArray* theDirs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([theDirs count] > 0)
{
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
NSURL* appSupportDir = (NSURL*)[theDirs objectAtIndex:0];
NSString* appBundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL* appDataDir = [[appSupportDir URLByAppendingPathComponent:appBundleID]
URLByAppendingPathComponent:#"Data"];
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
NSURL* backupDir = [appDataDir URLByAppendingPathExtension:#"backup"];
// Perform the copy asynchronously.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
// Just try to copy the directory.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if ([theFM removeItemAtURL:backupDir error:&anError]) {
// If the operation failed again, abort for real.
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
// Report the error....
}
}
}
});
}
}
Any thoughts?
You forgot to retry the copy operation after deleting an existing backup. Also, "catch let error" can be written as just "catch" because the error will be automatically assigned to a constant named "error" if you don't specify a catch pattern. Here is your code with these changes:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
do {
// Just try to copy the directory.
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch CocoaError.fileWriteFileExists {
// Error occurred because a previous backup directory
// already exists. Delete the old directory and try again.
do {
try fileManager.removeItem(at: backupDir)
} catch {
// The delete operation failed, abort.
print("Deletion of existing backup failed. Abort with error: \(error)")
return
}
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy operation failed again, abort.
print("Copy operation failed again. Abort with error: \(error)")
}
} catch {
// The copy operation failed for some other reason, abort.
print("Copy operation failed for other reason. Abort with error: \(error)")
}
}
}
If you want a translation that's closer to the Objective-C original, where there's only one error output, try this:
func backupMyApplicationData() {
// Get the application's main data directory
let directories = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard
directories.count > 0,
let appSupportDir = directories.first,
let bundleID = Bundle.main.bundleIdentifier
else {
return
}
// Build a path to ~/Library/Application Support/<bundle_ID>/Data
// where <bundleID> is the actual bundle ID of the application.
let appDataDir = appSupportDir.appendingPathComponent(bundleID).appendingPathComponent("Data")
// Copy the data to ~/Library/Application Support/<bundle_ID>/Data.backup
let backupDir = appDataDir.appendingPathExtension("backup")
// Perform the copy asynchronously.
DispatchQueue.global(qos: .default).async { _ in
// It's good habit to alloc/init the file manager for move/copy operations,
// just in case you decide to add a delegate later.
let fileManager = FileManager()
// Just try to copy the directory.
if (try? fileManager.copyItem(at: appDataDir, to: backupDir)) == nil {
// If an error occurs, it's probably because a previous backup directory
// already exists. Delete the old directory and try again.
if (try? fileManager.removeItem(at: backupDir)) != nil {
do {
try fileManager.copyItem(at: appDataDir, to: backupDir)
} catch {
// The copy retry failed.
print("Failed to backup with error: \(error)")
}
}
}
}
}
Your translation of the Objective-C original is incorrect, as has been pointed out in another answer. However, it seems that that is not really your question. It seems that your question is about the nesting.
To answer it, just look at the original that you are trying to imitate:
NSFileManager* theFM = [[NSFileManager alloc] init];
NSError* anError;
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
if ([theFM removeItemAtURL:backupDir error:&anError]) {
if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
}
}
}
Notice anything? Nesting. So the only difference between the structure of your code and the structure of the original is what is nested. The Objective-C original makes a nest of if clauses. Your Swift translation makes a nest of do/catch blocks — as it must do, since e.g. Objective-C copyItemAtURL returns a BOOL whereas Swift copyItem(at:) does not — if there's a problem, it throws.
So I think we may conclude that nesting is exactly the right thing to do. Indeed, what's wrong with your code (the reason it isn't an accurate translation of the original) is that it doesn't nest deep enough!
You could try to wrestle at least one of the catch blocks out of existence by substituting an if block testing what kind of error this is, but you would still be nesting, so why bother? You would just be throwing away all the elegance and clarity of the do/catch construct.
In Swift 2 and 3, there have 3 way to use a method that may generate errors.
If error happened, tell me.
do {
try something()
try somethingElse()
print("No error.")
} catch {
print("Error:", error)
}
I don't care about error. If error happen, just return nil.
try? something()
I don't believe this will have any error. If error happen, crash my app please.
try! something()

FileManager.createDirectory fails with NSCocoaErrorDomain Code: 518

I'm doing
let tempDirectory = URL(string: "\(NSTemporaryDirectory())video/")!
do {
try FileManager.default.createDirectory(
at: tempDirectory,
withIntermediateDirectories: true)
} catch { report(error) }
and that's often throwing an NSCocoaErrorDomain Code: 518.
Any idea of the reason? I thought that could because there's already something there, so I added
var isDir: ObjCBool = false
if FileManager.default.fileExists(
atPath: tempDirectory.absoluteString,
isDirectory: &isDir
) {
if isDir.boolValue {
print("Temp directory exists on launch")
}
else {
print("Temp directory exists on launch and is a file")
}
return
}
but that doesn't seem to catch anything
Your building of tempDirectory isn't correct. You want:
let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory()). appendingPathComponent("video")
The issue with your code is that you were not passing a value URL string to URL(string:). Since you have a file path you need to use URL(fileURLWithPath:). And build paths/URLs using the provided methods to ensure slashes and other parts are added correctly.
Print your value of tempDirectory from your original code and then print the new value from the code in my answer. Note the key difference.
Your URL will be something like:
/var/...
and it may be missing the slash before "video".
The correct file URL will be something like:
file:///var/...

Cannot find file in app extension's shared container

I want to write log file at my extension, and read it at my app.
For this purpose, I'm using shared groups (so both the app and the extension would be able to read from the same file)
I wrote the following code:
Extension:
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs")
let logFileManager = DDLogFileManagerDefault(logsDirectory: extensionLogDirectory)
PacketTunnelProvider.fileLogger = DDFileLogger(logFileManager: logFileManager)
PacketTunnelProvider.fileLogger!.rollingFrequency = 60*60*12
PacketTunnelProvider.fileLogger!.logFileManager.maximumNumberOfLogFiles = 1
DDLog.addLogger(PacketTunnelProvider.fileLogger)
App (just to read the log file):
let fileManager = NSFileManager.defaultManager()
let containerUrl = fileManager.containerURLForSecurityApplicationGroupIdentifier("group.MyCompany.MyProj")
if let extensionLogDirectory = containerUrl?.path?.stringByAppendingString("AppExtensionLogs") {
do {
let directoryContents = try fileManager.contentsOfDirectoryAtPath(extensionLogDirectory)//always fails
for file in directoryContents {
let path = extensionLogDirectory.stringByAppendingString(file)
do {
let fileContents = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
NSLog("file: \(fileContents)")
}
catch {/* error handling here */
}
}
}
catch {/* error handling here */
NSLog("nope!")
}
But, something now right - it's seems like contentsOfDirectoryAtPath always fails with "no such file" error
What's wrong in this code?
The problem is unrelated to app extensions or CocoaLumberjack.
stringByAppendingString just concatenates strings, so that the path
separator "/" is missing in the generated directory name.
There was a dedicated method stringByAppendingPathComponent, which however
has been deprecated in Objective-C and is no longer available in Swift.
You should operate on the URL by using URLByAppendingPathComponent
instead:
let extensionLogDirectory = containerUrl?.URLByAppendingPathComponent("AppExtensionLogs").path

Resources