18

I'm learning swift and wondering what's the best way to list all files with absolute path in a directory including ones from subfolders(files only)

I tried with following, but it seems listing all contents names even folder names without full paths.

let paths = FileManager.default.subpaths(atPath: folderPath)
    for p in paths! {
       print p
    }
}

as well as

let items = try fm.contentsOfDirectory(atPath: folderPath)

googling didn't come out with any working methods.

this is for macOS 10.14

any help is appreciated!

thanks

hippietrail
  • 15,848
  • 18
  • 99
  • 158
ikel
  • 1,790
  • 6
  • 31
  • 61

2 Answers2

45

FileManager has also a method for a deep search: enumerator(at:includingPropertiesForKeys:options:errorHandler:)

To get only the files you have to iterate the enumerator and filter the files

let url = URL(fileURLWithPath: "/path/to/directory")
var files = [URL]()
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
    for case let fileURL as URL in enumerator {
        do {
            let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
            if fileAttributes.isRegularFile! {
                files.append(fileURL)
            }
        } catch { print(error, fileURL) }
    }
    print(files)
}

It's highly recommended to use URLs rather than string paths.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • got var files = [URL]() Cannot invoke value of function type with argument list '()' – ikel Aug 24 '19 at 18:29
  • ok yes, it works in playground, I changed URL to NSURL, then it works, thanks a lot – ikel Aug 24 '19 at 18:45
  • Don't use `NSURL` in Swift. Do you have a custom object `URL` in your project? If yes rename it. – vadian Aug 24 '19 at 18:48
  • i don't have a custom object URL, but using URL gives ambiguous in context error somehow – ikel Aug 24 '19 at 19:18
  • I see what's wrong, there is Utility package which contains URL too – ikel Aug 24 '19 at 19:38
  • hmm, the file path I get contains %20 which I can't use, any way to get real file path? – ikel Aug 24 '19 at 21:20
  • 1
    `var files = [String]()` and `files.append(fileURL.path)` – vadian Aug 25 '19 at 04:43
  • While the accepted answer here is best since it uses URLs, a similar and related idea to consider here (link at end). It allows for differentiating between _files or folders_ as well as 'other' types of files including symbolic links. It's also concise to easily `print(element)` in the console. I also recommend `elements.sort()` if using a string array. https://stackoverflow.com/a/55697010/4970749 – bubbaspike Aug 17 '22 at 15:23
2

FileManager is right object

If you are using swift 5 or above could take advantage of AsyncStream. Below a simple Playground

import Foundation
import Combine

// Recursive iteration     
func walkDirectory(at url: URL, options: FileManager.DirectoryEnumerationOptions ) -> AsyncStream<URL> {
    AsyncStream { continuation in
        Task {
            let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: nil, options: options)
                
            while let fileURL = enumerator?.nextObject() as? URL {
                if fileURL.hasDirectoryPath {
                    for await item in walkDirectory(at: fileURL, options: options) {
                        continuation.yield(item)
                    }
                } else {
                    continuation.yield( fileURL )
                }
            }
            continuation.finish()
        }
    }
}


// use it     
let path = URL( string: "<your path>" )

let options: FileManager.DirectoryEnumerationOptions = [.skipsHiddenFiles, .skipsPackageDescendants]
    
Task {
        
    let swiftFiles = walkDirectory(at: path!, options: options).filter {
        $0.pathExtension == "swift"
    }

    for await item in swiftFiles {
        print(item.lastPathComponent)
    }
        
}

bsorrentino
  • 1,413
  • 11
  • 19