Jinny Jinny - 7 months ago 13
Swift Question

Swift - The file path is unavailable except for first time use

Below code is work. The images are saved successfully in document directory, but the problem is only first time the collectionViewController can load images with path successfully. I have to delete all images to store new images or it will show the error message


"fatal error: unexpectedly found nil while unwrapping an Optional
value".


Because the path is unavailable,
readnsdata = NSData(contentsOfFile: filepath)!
will cause error.

I have no idea why only the first time it can work.


path :
"/var/mobile/Containers/Data/Application/29306029-BDCF-4BEA-93A6-D5626CBAAA90/Documents/x.jpg"


func writeNSDataToDisk(imageData:NSData){

let myindex = imgPathArray.count
let fileName = "\(self.imgPathArray.count)"
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let docs: String = paths[0] as String
let filepath: String = (docs as NSString).stringByAppendingPathComponent("\(fileName).jpg")
let test = imageData.writeToFile(filepath, atomically: true)
if test {
self.imgPathArray.insert(filepath, atIndex: myindex)
print("The picture \(fileName).jpg is been saved.")
self.readORwriteList(true)//write list to txt file
}
print(self.imgPathArray)
}


func readNSDataFromDisk(fileIndex:Int) -> NSData{
let checkValidation = NSFileManager.defaultManager()
var readnsdata = NSData()
if (fileIndex <= self.imgPathArray.count) {
let filepath = self.imgPathArray[fileIndex]
if (checkValidation.fileExistsAtPath(filepath)){
print("File is available")
print("load \(fileIndex).jpg,filepath is \(filepath)")
readnsdata = NSData(contentsOfFile: filepath)!
if readnsdata.length != 0 {
getImageProperties(readnsdata)
}
}
else{
print("File is not available!!!")
}
}
return readnsdata
}





The solution to my problem :

Instead of storing absolute file path, I name the files in a regular way and search them by their name. There is no need to store path.

The URLs for the files are now constructed relative to the Documents directory URL every time the app is run.

Thanks

Answer

First a side note. Apple's docs specifically recommend against using fileExistsAtPath the way you're doing it here.

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.

Try replacing this…

if (checkValidation.fileExistsAtPath(filepath)){
    print("File is available")
    print("load \(fileIndex).jpg,filepath is \(filepath)")
    readnsdata = NSData(contentsOfFile: filepath)!
    if readnsdata.length != 0 {
        getImageProperties(readnsdata)
    }
}
else{
    print("File is not available!!!")
}

…with this…

do {
    readnsdata = try NSData(contentsOfFile: filepath, options: .DataReadingMappedIfSafe)
    if readnsdata.length != 0 {
        getImageProperties(readnsdata)
    }
}
catch let e {
    print("Couldn't read file at \(filepath) because \(e)")
}

This approach gives you the information you were looking for without having to speculate. Just run your code and see what happens when the NSData initializer throws! :)


[Update: Off-topic opinion]

While it's a good habit not to sprinkle a long method with returns, there's not a lot going on here. Personally, I think the code comes out more readable without the temporary readnsdata variable. This way, imo, both the happy path and the default return values are clear on first reading:

func readNSDataFromDisk2(fileIndex:Int) -> NSData{
    if (fileIndex <= self.imgPathArray.count) {
        let path = self.imgPathArray[fileIndex]
        do {
            let data = try NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe)
            if data.length != 0 {
                getImageProperties(data)
            }
            return data
        }
        catch let e {
            print("Couldn't read file at \(path) because \(e)")
        }
    }
    return NSData()
}