TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"} when using FileImporter then read the content

Forums > SwiftUI

Hi all,

I'm working on something I thought would be simple, and it turns out to be taking way longer than I expected and spent hours looking for a similar issue that would resolve my issue, to no avail.

I've followed a tutorial to import the content of a file that is part of the application. That part works fine, no issues there. Now I want to update this import to allow the user to select a file from the Files application and then reads its content, the same way it does with the file part of the application.

I've tried several things, so my code might be a little messed up, and not optimal yet.

This is the part of the code that works, when using the file part of my application - note the path using Bundle.main.path (I stopped pasting right at the point it fails, the function continues and that part is not relevant eventually):

func loadCSVData() -> [MeasuresHistory]{
    var csvToStruct = [MeasuresHistory]()

    guard let filePath  = Bundle.main.path(forResource: "MeasuresHistory", ofType: "csv") else {
        print("Error: file not found!")
        return[]
    }

    // Convert the contents of the file into a very long string
    var data = ""
    do {
        data = try String(contentsOfFile: filePath)
    }
    catch {
        print(error)
        return[]
    }

Now how I modified the function, to attempt to make it work with a custom path from FileImporter (I'm using @AppStorage for now, I know it's not the best, but wanted to at least be able to get the value retrieved with FileImporter available in that function for now):

func loadCSVData() -> [MeasuresHistory]{
    var csvToStruct = [MeasuresHistory]()
    @AppStorage("selectedFilhPath") var selectedFilePath = ""

    guard let filePath  = Bundle.main.path(forResource: "MeasuresHistory", ofType: "csv") else {
        print("Error: file not found!")
        return[]
    }

    // Convert the contents of the file into a very long string
    var data = ""
    do {
        if selectedFilePath != "" {
            data = try String(contentsOfFile: selectedFilePath) // <--- this is the line that generates the error
        } else {
            data = try String(contentsOfFile: filePath)
        }
    }
    catch {
        print(error)
        return[]
    }

This is the error:

Error Domain=NSCocoaErrorDomain Code=260 "The file “MeasuresHistory.csv” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/Users/{myUserID}/Library/Developer/CoreSimulator/Devices/{someObfuscatedID}/data/Containers/Shared/AppGroup/{someDifferentObfuscatedID}/File%20Provider%20Storage/MeasuresHistory.csv, NSUnderlyingError=0x6000006fc4e0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

Adn this is how I request the user to select the file to import, in another file/view:

Button("Import") {
                        presentImporter = true
                    }
                    .fileImporter(isPresented: $presentImporter, allowedContentTypes: [.commaSeparatedText]) { result in
                        switch result {
                        case .success(let filePath):
                            print(filePath.path())
                            selectedFilePath = filePath.path()
                            loadCSVData()
                        case .failure(let error):
                            print(error)
                        }
                    }

The print of the filePath.path() is that one:

/Users/{myUserID}/Library/Developer/CoreSimulator/Devices/{someObfuscatedID}/data/Containers/Shared/AppGroup/{someDifferentObfuscatedID}/File%20Provider%20Storage/MeasuresHistory.csv

To me, it seems the file can return its path and it could be used to read the content of it. I'm lost as to what I'm not understanding to make it work by selecting the file. I'm using the iOS Simulator, not a real device for the testing, if that makes a difference.

Thank you very much for any assistance you can provide.

1      

Hi @mezzomix23,

Bundle - a representation of the code and resources stored in a bundle directory on disk. Bundle is used when we need access files added to our project during development. It is used for app's files, code and resources.

You app cannot save files directly to bundle otherwise Apple OS wouldn't be so secure if everyone could access files in their directories. As you can see from your error you are trying to access file via bundle which is in absolutely different directotry File%20Provider%20Storage/MeasuresHistory.csv. And as it does not exist in bundle the error is thrown.

I haven't tried this workaround but just my considerations. Using .fileImporter to get hold of contents of the file if needed or otherwise try to save it in userDomain location. In the example below I was using it to display the data on the screen instead of saving. If you need to display it direcly maybe it is the good way to start.

        .fileImporter(isPresented: $openImport, allowedContentTypes: [.plainText]) { result in
            if let fileURL = try? result.get() {
                if fileURL.startAccessingSecurityScopedResource() {
                    if let data = try? Data(contentsOf: fileURL) {
                        if let text = String(data: data, encoding: .utf8) {
                            document.documentText = text
                        }
                    }
                    fileURL.stopAccessingSecurityScopedResource()
                }
            }
        }

Then save the imported file in DocumentDirectory as in this lesson https://www.hackingwithswift.com/example-code/strings/how-to-save-a-string-to-a-file-on-disk-with-writeto

Then you can have access to "your" file via file manager, as you will have the file saved in previous step.

var manager: FileManager   
var docURL: URL    
init() { 
  manager = FileManager.default
  docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
  let fileURL = docURL.appendingPathComponent("textdata.dat")
  let filePath = fileURL.path 
  if manager.fileExists(atPath: filePath) { 
    if let content = manager.contents(atPath: filePath) { 
      if let text = String(data: content, encoding: .utf8) { 
        textInFile = text 
        }
       }
      }
     }

1      

Hi @ygeras,

Thank you very much for your answer, it really helped.

Indeed, I do not need to save the file in my app once imported, just read its content, so that piece of code, with some adjustements for my needs, did the trick:

.fileImporter(isPresented: $openImport, allowedContentTypes: [.plainText]) { result in
            if let fileURL = try? result.get() {
                if fileURL.startAccessingSecurityScopedResource() {
                    if let data = try? Data(contentsOf: fileURL) {
                        if let text = String(data: data, encoding: .utf8) {
                            document.documentText = text
                        }
                    }
                    fileURL.stopAccessingSecurityScopedResource()
                }
            }
        }

Have a great week-end!

1      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.