Listing directory contents

Extending the getcwd example in another thread I would like to list all files in that cwd.
To recall:

    var buf = [Int8](repeating: 0, count: 256)
            return buf.withUnsafeMutableBufferPointer { buf -> String? in 
                guard let cwdRef = getcwd(buf.baseAddress!, buf.count) else { return nil }
            return String(cString: cwdRef)
     }
}

Now with the thus obtained cwd ("/" is printed from the App in iPhone), I would like to
list the directory contents. I grabbed this piece of code from some stackoverflow thread, but it is in so far not usable since it is using some .documentDirectory - Swift language question: what does the .documentDirectory notation mean (leading dot)?
And it deals with URLs.
I would like to pass the directory name to the function and let it print its content.

func listDir(dir: String) {
    // Create a FileManager instance
    
    let fileManager = FileManager.default
    let documentsURL = fileManager.urls(for: .documentDirectory, 
        in:.userDomainMask)[0]
    do {
        let fileUrls = try fileManager.contentsOfDirectory(at:documentsURL, includingPropertiesForKeys: nil)
        // process files
        print(fileUrls)
    } catch {
        print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
    }
    
}

Addendum: I took a look at iOS file management with FileManager in protocol-oriented Swift 4 – iOS Brain

What is the idea behind using "URL"s all the way instead of "files"?

In Swift, when you see a symbol with a leading dot (in this case .documentDirectory, it means that whatever is infront of the dot is inferred by compiler, usually it’s the type that is expected from that location.

You’re trying to call fileManager.urls(for: .documentDirectory, in: .userDomainMask) which has 1 overload:

fileManager.urls(for directory: FileManager.SearchPathDirectory, in domainMask: FileManager.SearchDomainMask) -> [URL]

So the compiler infers what you write into

fileManager.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchDomainMask.userDomainMask) -> [URL]

FileManager separate URLs into domain, for example, systemDomain(/), userDomain(~/), each domain may contain a common directory specify by SearchPathDirectory, such as downloadDirectory (./Downloads).

Your code is trying to access a download folder in user domain. In macOS, it would resolve into ~/Downloads, but I suspect that there’s no such folder in iOS file system.
Though I don’t know what’s wrong with the code (since I can’t test it and you simply say that it doesn’t work), that’d be my guess.

Do note that the actual base path may be different due to other factors such as sandboxing, etc.

Note: your code is trying to list a content in documentURL, not dir, you’ll need to convert dir to URL, then use it instead of documentURL

1 Like

What is the idea behind using "URL"s all the way instead of "files"?

I think you mean “instead of path strings?”, and I can certainly explain that. A while back (Mac OS X 10.5 IIRC) Cocoa standardised on using URLs to represent file system objects rather than paths. There’s a number of reasons for this, some of which are now less relevant but some of which are getting more relevant as the years pass. For example:

  • Cocoa needs a way to represent items by path and by reference, and URLs allow for that. This is why NSURL has the isFileURL property, which you use in preference to testing for the file URL scheme.

    This usage pattern has fallen by the wayside somewhat but it still crops up from time to time. For example, I was recently working on a macOS app and my drag’n’drop code failed because it wasn’t handling file reference URLs properly.

  • URLs provide a handy place to cache file system properties, something that’s exploited by the ‘resource value’ APIs.

  • More recently we’ve used the flexibility of URLs to help in implementing security scopes.

New APIs that reference file system objects should take URLs not path strings. We’ve also made an effort to supplement path-based APIs with new URL-based ones, although that coverage is not 100% and it only applies at the Cocoa layer and not, for example, when working with BSD APIs.

Personally I used URLs internally in all my code and then convert to or from paths only when I need to work with the rare API that doesn’t support URLs.


Extending the getcwd example in another thread I would like to list
all files in that cwd.

You don’t need to deal with the current working directly explicitly in that case. Rather, you can just build a relative URL from the path . and go from there.

let contentsOfCurrentWorkingDirectory = try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: "."), includingPropertiesForKeys: nil, options: [])

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

(Swift 4.2, Xcode 10.1)

Thanks.
So this is the code I'm trying but I still get errors (your example was incomplete regarding the try clause):

import Foundation


class ViewController: UIViewController {
    @IBOutlet weak var ScrollView: UIScrollView!
    @IBOutlet weak var textView: UITextView!
    var textFromFile = String()
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.t
      
        //let fd = init_udpC()
       let c = cwd()
        textView.text =  "The current working directory is \(c ?? "nil")\n"
        listDir(dir: c )  // <-- I'm getting a compiler error here 
//Value of optional type 'String?' must be unwrapped to a value of type 'String'
    }
    @IBAction func ClearTextArea(_ sender: Any) {
        textView.text=""
    }
    
    @IBAction func sendUDP(_ sender: Any) {
        print("calling C")
        udpC()
    }
    private func udpC_init(){
        
    }
    func cwd() -> String? {
        var buf = [Int8](repeating: 0, count: 256)
            return buf.withUnsafeMutableBufferPointer { buf -> String? in
              guard let cwdRef = getcwd(buf.baseAddress!, buf.count) else { return nil }
            return String(cString: cwdRef)
        }
    }
    func listDir(dir: String) {
        // Create a FileManager instance
      let contentsOfCurrentWorkingDirectory = try?FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: dir), includingPropertiesForKeys: nil, options: [])
        
            // process files
        contentsOfCurrentWorkingDirectory?.forEach() { url in
             contentsOfCurrentWorkingDirectory?.forEach() { url in
            textView.text.append(contentsOf: (url.path + "\n"))
        }
        
    }
    
}

I'm getting a kind of "default label" in the code when I do an autocorrect in the code editor.

EDIT: I can make my code working when I write

listDir(dir: c! )

I added this following one of the autocorrection fixes the compiler was suggesting. But I don't understand why this then works.

What is this wrapping/unwrapping behind it? And why is that an "optional" String actually?

Regarding wrapping/unwrapping, please see here, and Optional Section here.

  • It’s because your cwd returns String? which is Optional.
    That means it may be a proper String, or it may be nothing at all (nil).

  • When you write let c = cwd(), c is now also String?.

  • The problem is then that listDir(dir: String) wants a proper String, not String?.

  • You fix that in 2 common ways

    • Tell compiler you know that it is a valid string and write c!, this is known as unwrapping. If c is a valid string, it’ll be converted to String, otherwise, the application will crash
    • Check if it’s a proper string, handle each case separately
      if let someName = c {
          // c is a valid string, and someName has that value
      } else {
          // c is nil, handle appropriately
      }
      
      you can even have someName be the same as c and it’ll shadow the original c.
1 Like