Iterate through files in a folder and its subfolders using Swift's FileManager

SwiftCocoaNsfilemanager

Swift Problem Overview


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.

Swift Solutions


Solution 1 - Swift

Use the nextObject() method of enumerator:

while let element = enumerator?.nextObject() as? String {
    if element.hasSuffix("ext") { // checks the extension
    }
}

Solution 2 - Swift

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

let fileManager = FileManager.default

do {
    let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
    let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    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)
}

Solution 3 - Swift

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!)")
}

Solution 4 - Swift

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

Solution 5 - Swift

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)
}

Solution 6 - Swift

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")
            }
        }
        
    }
    

Solution 7 - Swift

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)
    }
})

Solution 8 - Swift

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

Solution 9 - Swift

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
}

Solution 10 - Swift

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 ....
    }
}

Solution 11 - Swift

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

Solution 12 - Swift

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
    }
}

Solution 13 - Swift

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
    }
}

Solution 14 - Swift

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIacopo BoccalariView Question on Stackoverflow
Solution 1 - SwiftpNreView Answer on Stackoverflow
Solution 2 - SwiftvadianView Answer on Stackoverflow
Solution 3 - SwiftRainier WolfcastleView Answer on Stackoverflow
Solution 4 - SwiftneoneyeView Answer on Stackoverflow
Solution 5 - SwiftAlex ShubinView Answer on Stackoverflow
Solution 6 - SwiftingcontiView Answer on Stackoverflow
Solution 7 - Swiftuser3441734View Answer on Stackoverflow
Solution 8 - Swiftuser1398498View Answer on Stackoverflow
Solution 9 - SwiftRanjithView Answer on Stackoverflow
Solution 10 - SwiftdawgView Answer on Stackoverflow
Solution 11 - SwiftdougzillaView Answer on Stackoverflow
Solution 12 - SwiftChris AmelinckxView Answer on Stackoverflow
Solution 13 - SwiftJoe MaherView Answer on Stackoverflow
Solution 14 - SwiftDavid HitchenView Answer on Stackoverflow