Search code examples
iosswiftplistnsfilemanager

Getting "The file couldn’t be opened because there is no such file" error from FileManager even though it's actually opened


I'm retrieving a plist file, updating it, and writing it to disk.

1) Retrieval

func pListURL() -> URL? {
     guard let result = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("somePlist.plist") else { return nil }
     return result
 }

2) Update

var data: [String: Int] = [:]
if let url = pListURL() {
    do {
        let dataContent = try Data(contentsOf: url)
        if let dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
            data = dict
        }
    } catch {
        print(error)
    }
}

// update code

3) Write

if let path = pListURL() {
    do {
        let plistData = try PropertyListSerialization.data(fromPropertyList: data, format: .xml, options: 0)
        try plistData.write(to: path)
    } catch {
        print(error)
    }
}

The odd thing is I get an error that says:

The file “somePlist.plist” couldn’t be opened because there is no such file.

even though when I check the plist, it's actually properly created and updated as it should be. As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.


Solution

  • As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.

    No, it means the directory is created but the file is not created.

    In the update part ignore the couldn’t be opened error and write the (new) data to disk or check if the file exists with fileExists(atPath.

    And you can declare the return value in pListURL as non-optional. It's guaranteed that the folder Documents exists

    func pListURL() -> URL {
         return try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somePlist.plist")
    }
    

    Update:

    These are reliable versions of update and write

    func update(dictionary: [String:Int]) {
        let url = pListURL()
        guard FileManager.default.fileExists(atPath: url.path) else { write(dictionary: dictionary); return }
        do {
            let dataContent = try Data(contentsOf: url)
            if var dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
                for (key, value) in dictionary {
                    dict.updateValue(value, forKey: key)
                }
                write(dictionary: dict)
            } else {
                write(dictionary: dictionary)
            }
        } catch {
            print(error)
        }
    }
    
    func write(dictionary: [String:Int]) {
        let url = pListURL()
        do {
            let plistData = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
            try plistData.write(to: url)
        } catch {
            print(error)
        }
    }