85

I'm quite new to programming a Swift and I'm trying to iterate through the files in a folder. I took a look at the answer here and tried to translate it to Swift syntax, but didn't succeed.

let fileManager = NSFileManager.defaultManager()
let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)

for element in enumerator {
    //do something
}

the error I get is:

Type 'NSDirectoryEnumerator' does not conform to protocol 'SequenceType'

My aim is to look at all the subfolders and files contained into the main folder and find all the files with a certain extension to then do something with them.

14 Answers 14

88

Use the nextObject() method of enumerator:

while let element = enumerator?.nextObject() as? String {
    if element.hasSuffix("ext") { // checks the extension
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Couldn't get this to work without optional chaining as per user1398498's answer below.
Having just tried this out, I got nothing but an infinite loop and had to go with Rainier's approach roughly.
Very strange that a rather crude while is favored over the dense for solution. (Oh, I see I already commented on the other solution similarly - anyway)
For those who use enumerator(at:, includingPropertiesForKeys:, options:) take into account that the element is actually a URL not a String, so if you use that code it will return nothing. while let element = enumerator?.nextObject() as? URL {...}
Good reference. I found it from the below link too. developer.apple.com/documentation/foundation/filemanager/…
74

Nowadays (early 2017) it's highly recommended to use the – more versatile – URL related API

do {
    let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
    // let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) // old API prior to iOS 16+ / macOS 13+
    let documentsURL = URL.documentsDirectory // new API iOS 16+ / macOS 13+
    let enumerator = FileManager.default.enumerator(at: documentsURL,
                            includingPropertiesForKeys: resourceKeys,
                                               options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
                                                        print("directoryEnumerator error at \(url): ", error)
                                                        return true
    })!
    
    for case let fileURL as URL in enumerator {
        let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
        print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
    }
} catch {
    print(error)
}

4 Comments

Thank you for your thoroughness. (I had no luck at all with the path-typed version, since myURL.absolutePath is not a valid string-based path for the purposes of NSFileEnumerator and does not create a valid enumerator). For the list of URLResourceKeys, look at developer.apple.com/documentation/foundation/urlresourcekey
This is a good example of how to execute a deep search and worked great for me. Thank you for the nice contribution.
This code is very helpful. But I want to get progress of traversing files at least first sub level directories in root folder (Of course, I understand that we cannot get the exact total number of files until we have enumerated all of them. For this reason, it would be nice to know the acquisition progress of "at least the first sub-level".). Is there any way to get the progress?
For anyone googling here in re this famous answer, if you happen to need the SIZE of a directory, save you typing: stackoverflow.com/a/78765546/294884
20

I couldn't get pNre's solution to work at all; the while loop just never received anything. However, I did come across this solution which works for me (in Xcode 6 beta 6, so perhaps things have changed since pNre posted the above answer?):

for url in enumerator!.allObjects {
    print("\((url as! NSURL).path!)")
}

5 Comments

Funny that this solution has so few up-votes since it has a fairly more easy syntax than the current favorite.
the reason why it has votes because it relies on forced unwrapping of enumerator (enumerator!) as opposed to enumerator? in top voted answer.
Beware this is unusable large directories - it can hang for minutes.
Stop force unwrapping.
In 2022 Xcode complains Reference to member 'allObjects' cannot be resolved without a contextual type
14

my two cents from previously anwers.. more swifty and with optionals:

 let enumerator = FileManager.default.enumerator(atPath: folderPath)
    while let element = enumerator?.nextObject() as? String {
        print(element)

        if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType{

            switch fType{
            case .typeRegular:
                print("a file")
            case .typeDirectory:
                print("a dir")
            }
        }

    }

1 Comment

This is THE most useful entry here as it highlights picking apart the entries by dir and file types. That seems to be a very forgotten part of functionality like this.
10

returns all files in a directory + in subdirectories

import Foundation

let path = "<some path>"

let enumerator = FileManager.default.enumerator(atPath: path)

while let filename = enumerator?.nextObject() as? String {
        print(filename)
}

4 Comments

It won't go inside subdirectories nor get hidden files nor follow symbolic links
how do we get full path instead of file names?
What kind of path do you use? In my case, I have "mydir" directory in the xcode project. When I use let path = "mydir", nothing happends. I'using swift 5.
It's a full path. Is there a way to use a relative path so that it can be contained in the project folder?
10

Swift3 + absolute urls

extension FileManager {
    func listFiles(path: String) -> [URL] {
        let baseurl: URL = URL(fileURLWithPath: path)
        var urls = [URL]()
        enumerator(atPath: path)?.forEach({ (e) in
            guard let s = e as? String else { return }
            let relativeURL = URL(fileURLWithPath: s, relativeTo: baseurl)
            let url = relativeURL.absoluteURL
            urls.append(url)
        })
        return urls
    }
}

Based on code from @user3441734

Comments

6

Swift 3

let fd = FileManager.default
fd.enumerator(atPath: "/Library/FileSystems")?.forEach({ (e) in
    if let e = e as? String, let url = URL(string: e) {
        print(url.pathExtension)
    }
})

1 Comment

When I used this code I found that directories/files with spaces in the path were ignored. Any idea why this happens? I then tried @vadian's code and it works as expected.
5

In case that you are getting the

'NSDirectoryEnumerator?' does not have a member named 'nextObject' error

the while loop should be:

while let element = enumerator?.nextObject() as? String {
  // do things with element
}

It has something to do with optional chaining

Comments

5

SWIFT 3.0

Returns all files with extension in the Directory passed & its subdirectories

func extractAllFile(atPath path: String, withExtension fileExtension:String) -> [String] {
    let pathURL = NSURL(fileURLWithPath: path, isDirectory: true)
    var allFiles: [String] = []
    let fileManager = FileManager.default
    let pathString = path.replacingOccurrences(of: "file:", with: "")
    if let enumerator = fileManager.enumerator(atPath: pathString) {
        for file in enumerator {
            if #available(iOS 9.0, *) {
                if let path = NSURL(fileURLWithPath: file as! String, relativeTo: pathURL as URL).path, path.hasSuffix(".\(fileExtension)"){
                    let fileNameArray = (path as NSString).lastPathComponent.components(separatedBy: ".")
                    allFiles.append(fileNameArray.first!)
                }
            } else {
                // Fallback on earlier versions
                print("Not available, #available iOS 9.0 & above")
            }
        }
    }
    return allFiles
}

Comments

2

Updating for Swift 3:

let fileManager = FileManager()     // let fileManager = NSFileManager.defaultManager()
let en=fileManager.enumerator(atPath: the_path)   // let enumerator:NSDirectoryEnumerator = fileManager.enumeratorAtPath(folderPath)

while let element = en?.nextObject() as? String {
    if element.hasSuffix("ext") {
        // do something with the_path/*.ext ....
    }
}

Comments

1

Adding to vadian's response -- the Apple docs mention that Path-based URLs are simpler in some ways, however file reference URLs have the advantage that the reference remains valid if the file is moved or renamed while your app is running.

From the documentation for "Accessing Files and Directories":

"Path-based URLs are easier to manipulate, easier to debug, and are generally preferred by classes such as NSFileManager. An advantage of file reference URLs is that they are less fragile than path-based URLs while your app is running. If the user moves a file in the Finder, any path-based URLs that refer to the file immediately become invalid and must be updated to the new path. However, as long as the file moved to another location on the same disk, its unique ID does not change and any file reference URLs remain valid."

https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html

Comments

0

If you want to categorically check whether an element is a file or a subdirectory:

let enumerator = FileManager.default.enumerator(atPath: contentsPath);
while let element = enumerator?.nextObject() as? String {             
   if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeRegular){
                //this is a file
   }
   else if(enumerator?.fileAttributes?[FileAttributeKey.type] as! FileAttributeType == FileAttributeType.typeDirectory){ 
                //this is a sub-directory
    }
}

Comments

0

Recently struggled with this when handling an array of urls, whether they be a directory or not (eg. drag and drop). Ended up with this extension in swift 4, may be of use

extension Sequence where Iterator.Element == URL {

    var handleDir: [URL] {
        var files: [URL] = []
        self.forEach { u in
            guard u.hasDirectoryPath else { return files.append(u.resolvingSymlinksInPath()) }
            guard let dir = FileManager.default.enumerator(at: u.resolvingSymlinksInPath(), includingPropertiesForKeys: nil) else { return }
            for case let url as URL in dir {
                files.append(url.resolvingSymlinksInPath())
            }
        }
        return files
    }
}

Comments

-3

Avoid reference URLs, while they do have some advantages as stated above, they eat system resources and if you’re enumerating a large filesystem (not that large actually) your app will hit a system wall quickly and get shutdown by macOS.

2 Comments

Doesn't answer the question, this should be more of a comment than an answer.
the real reason is that more an more Apple APIs DO want urls. (se for example all path-related calls)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.