Swift + macOS + Xcode : Encoding a string that represents a file path for contentsOfFile

Hello,

I have Swift code that lets me select and read a CSV file. I would like to be able to hard code the path to the CSV file as a string in the @IBAction function OpenPanel instead of calling getCSVPath. To do so I think may require encoding the file path string but am unsure how to accomplish this.
Below is my code.

Chris

import Cocoa

func getCSVPath() -> String {

let dialog = NSOpenPanel()
dialog.title = "Select a CVS file"
dialog.showsResizeIndicator = true
dialog.showsHiddenFiles = false
dialog.allowsMultipleSelection = false
dialog.canChooseDirectories = false
dialog.allowedFileTypes = ["csv"]

if (dialog.runModal() == NSApplication.ModalResponse.OK){
    let result = dialog.url
    if (result != nil) {
        return result!.path
    } else {
        return "nofile"
    }
} else {
    return "cancel"
}

}

func parsedCSVLine (thePath : String) -> Array {
do {
let content = try String(contentsOfFile: thePath)
let parsedLines : [String] = content.components(separatedBy: "\n")
return parsedLines
}
catch {
let tempArray : [String] = ["error"]
return(tempArray)
}
}

func getDates (inputArray : Array ) -> Array {
var outputArray : [String] =
for i in 1..<inputArray.count - 1 {
let lineArray = inputArray[i].components(separatedBy: ",")
outputArray.append(lineArray[0])
}
return outputArray
} // end function

func getValues(inputArray : Array) -> Array{
var outputArray : [Double] =
for i in 1..<inputArray.count - 1 {
let lineArray = inputArray[i].components(separatedBy: ",")
outputArray.append(Double(lineArray[1])!)
}
return outputArray
} // end function

//func myArrayFunc(inputArray:Array) -> Array {}
//first.append(contentsOf: second)

class ViewController: NSViewController {

@IBOutlet weak var nameField: NSTextField!
@IBOutlet weak var helloLabel: NSTextField!
@IBAction func sayButtonClicked(_ sender: Any) {
    
}

@IBAction func OpenPanel(_ sender: Any) {

    let path = getCSVPath()
    let parsedArray : [String] = parsedCSVLine(thePath: path)
    if parsedArray[0] == "error" {
        print ("error")
        return
    }

// let dateArray : [String] = getDates(inputArray: parsedArray)
// let valueArray : [Double] = getValues(inputArray: parsedArray)

    print(parsedArray)
    
    
    
 } // end of func OpenPanel

@IBAction func readCSV(_ sender: Any) {

    if let fileURL = Bundle.main.url(forResource: "Data", withExtension: "csv"){
            if let content = try? String(contentsOf: fileURL){
                let parsedCSV : [[String]] = content
                    .components(separatedBy: "\n")
                    .map({$0.components(separatedBy: ",")})
                print(parsedCSV)
            }
    } // end of topmost if statement
} // end of func readCSV

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override var representedObject: Any? {
    didSet {
    // Update the view, if already loaded.
    }
}

}

So then just declare a variable called path and initialize it to the file path. I don't understand what you're confused about.

@IBAction func OpenPanel(_ sender: Any) {
    let path = "path/to/file"  // <---
    let parsedArray : [String] = parsedCSVLine(thePath: path)
    if parsedArray[0] == "error" {
        print ("error")
        return
    }
}

I have tried let path = "/Users/Chris/Desktop/Data.csv" which is what getCSVPath() appears to return. Unfortunately contentsOfFile fails in parsedCSVLine() and the function returns "error" from the catch.
When I use the getCSVPath() everything works great. It returns a dialog.url.path which I am guessing is not the same as a string.

Chris

not just "error" but "permission error": that's because of app sandbox (App Sandbox | Apple Developer Documentation)

normal apps don't hardcode paths like this /Users/Chris/Desktop/xxx, so i guess this is just for test and you don't actually mind where the file is - if that's the case just store it in the bundle resources. or perhaps you may just switch app sandboxing off if this is for test on your computer only. otherwise read the linked article, there are a few ways to access files in various file system locations outside of app bundle.

So then why don't you print dialog.url.path to the console so you can see what it is? There's no need to guess.

dialog.url.path is /Users/Chris/Desktop/Data.csv.
I will read the link App Sandbox in Depth. Thanks.
The dialog box presented in the getCSVPath() function I assume is allowing me to penetrate the sandbox for it allows me to access the file.

Thank you everyone for the input.
Slowly but surely I will get better at Swift.

Chris

1 Like

See also Working with Files on iOS with Swift - AppyPie

With sandboxing, if you use an open panel, the fact that the user chose the file gives your app permission to access the file. Your app can't just go poking around in e.g. ~/Library/Mail/ without explicit user permission.

Even with sandboxing off, you may need to add the NSDesktopFolderUsageDescription key to your Info.plist to trigger the OS to prompt the user for permission to access the Desktop folder. There are similar keys for access the Documents folder and network volumes.

Even with sandboxing off

Right. My On File System Permissions DevForums post has more background on this.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple