FileManager Class Tutorial for macOS: Getting Started with the File System
In this tutorial, learn to use the FileManager class in a macOS app. Work with files, folders and navigate the file system while creating a working app. By Sarah Reichelt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
FileManager Class Tutorial for macOS: Getting Started with the File System
30 mins
File Spy
In this part of the tutorial, you’re going to build the File Spy app, which lets you select a folder and view a listing of every file or folder inside. Selecting any item will give you more details about it.

Download the starter app project, open it in Xcode and click the Play button in the toolbar, or press Command-R to build and run. The UI is already set up, but you’ll need to add the file management bits.
Your first task is to let the user select a folder and then list its contents. You’ll add some code behind the Select Folder button and use the NSOpenPanel class to select a folder.
In ViewController.swift, find selectFolderClicked in the Actions section and insert the following:
// 1
guard let window = view.window else { return }
// 2
let panel = NSOpenPanel()
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
// 3
panel.beginSheetModal(for: window) { (result) in
if result == NSFileHandlingPanelOKButton {
// 4
self.selectedFolder = panel.urls[0]
print(self.selectedFolder)
}
}
Here’s what’s going on in the code above:
- Check that you can get a reference to the window, since that’s where the
NSOpenPanelwill be displayed. - Create a new
NSOpenPaneland set some properties to only permit a single selection which must be a folder. - Display the
NSOpenPanelmodally in the window and use a closure to wait for the result. - If the result shows that the user clicked the OK button (the displayed button will have a different label depending on your locale), get the selected
URLand set a specificViewControllerproperty. For a quick temporary test, you print the selectedURLto the console. Ignore the warning on this line for now.
Build and run, click the Select Folder button and choose a folder. Confirm that the URL for the selected folder prints in the console.
Click the button again to open the dialog,but this time click Cancel. This will not print a selected URL.

Quit the app and delete the temporary print statement.
Folder Contents
Now that you can select a folder, your next job is to find the contents of that folder and display it.
The previous section of code populated a property named selectedFolder. Scroll to the top of the ViewController definition and check out the selectedFolder property. It’s using a didSet property observer to run code whenever its value is set.
The key line here is the one that calls contentsOf(folder:). Scroll down to the stub of this method, which is currently returning an empty array. Replace the entire function with the following:
func contentsOf(folder: URL) -> [URL] {
// 1
let fileManager = FileManager.default
// 2
do {
// 3
let contents = try fileManager.contentsOfDirectory(atPath: folder.path)
// 4
let urls = contents.map { return folder.appendingPathComponent($0) }
return urls
} catch {
// 5
return []
}
}
Stepping through what the code does:
- Get the
FileManagerclass singleton, just as before. - Since the
FileManagermethod can throw errors, you use ado...catchblock. - Try to find the contents of the folder
contentsOfDirectory(atPath:)and return an array of file and folder names inside. - Process the returned array using
mapto convert each name into a completeURLwith its parent folder. Then return the array. - Return an empty array if
contentsOfDirectory(atPath:)throws an error.
The selectedFolder property sets the filesList property to the contents of the selected folder, but since you use a table view to show the contents, you need to define how to display each item.
Scroll down to the NSTableViewDataSource extension. Note that numberOfRows already returns the number of URLs in the filesList array. Now scroll to NSTableViewDelegate and note that tableView(_:viewFor:row:) returns nil. You need to change that before anything will appear in the table.
Replace the method with:
func tableView(_ tableView: NSTableView, viewFor
tableColumn: NSTableColumn?, row: Int) -> NSView? {
// 1
let item = filesList[row]
// 2
let fileIcon = NSWorkspace.shared().icon(forFile: item.path)
// 3
if let cell = tableView.make(withIdentifier: "FileCell", owner: nil)
as? NSTableCellView {
// 4
cell.textField?.stringValue = item.lastPathComponent
cell.imageView?.image = fileIcon
return cell
}
// 5
return nil
}
Here’s what you do in this code:
- Get the
URLmatching the row number. - Get the icon for this
URL.NSWorkspaceis another useful singleton; this method returns the Finder icon for anyURL. - Get a reference to the cell for this table. The FileCell identifier was set in the Storyboard.
- If the cell exists, set its text field to show the file name and its image view to show the file icon.
- If no cell exists, return
nil.
Now build and run, select a folder and you should see a list of files and folders appear — hurray!

But clicking on a file or folder gives no useful information yet, so on to the next step.
Getting File Information
Open up the Finder and press Command-I to open a window with information about the file: creation date, modification date, size, permissions and so on. All that information, and more, is available to you through the FileManager class.

Back in the app, still in ViewController.swift, look for tableViewSelectionDidChange. This sets the property of the ViewController: selectedItem.
Scroll back to the top and look at where selectedItem is defined. As with selectedFolder, a didSet observer is watching for changes to this property. When the property changes, and if the new value is not nil, the observer calls infoAbout(url:). This is where you will retrieve the information for display.
Find infoAbout, which currently returns a boring static string, and replace it with the following:
func infoAbout(url: URL) -> String {
// 1
let fileManager = FileManager.default
// 2
do {
// 3
let attributes = try fileManager.attributesOfItem(atPath: url.path)
var report: [String] = ["\(url.path)", ""]
// 4
for (key, value) in attributes {
// ignore NSFileExtendedAttributes as it is a messy dictionary
if key.rawValue == "NSFileExtendedAttributes" { continue }
report.append("\(key.rawValue):\t \(value)")
}
// 5
return report.joined(separator: "\n")
} catch {
// 6
return "No information available for \(url.path)"
}
}
There are a few different things happening here, so take them one at a time:
- As usual, get a reference to the
FileManagershared instance. - Use
do...catchto trap any errors. - Use the FileManager Class’
attributesOfItem(atPath:)method to try to get the file information. If this succeeds, it returns a dictionary of type[FileAttributeKey: Any]FileAttributeKeys, which are members of a struct with a StringrawValue. - Assemble the key names & values into an array of tab-delimited strings. Ignore the
NSFileExtendedAttributeskey as it contains a messy dictionary that isn’t really useful. - Join these array entries into a single string & return it.
- If the
trythrows an error, return a default report.
Build and run again, select a folder as before, then click on any file or folder in the list:

You now get a lot of useful details about the file or folder. But there’s still more you can do!