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
NSOpenPanel
will be displayed. - Create a new
NSOpenPanel
and set some properties to only permit a single selection which must be a folder. - Display the
NSOpenPanel
modally 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
URL
and set a specificViewController
property. For a quick temporary test, you print the selectedURL
to 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
FileManager
class singleton, just as before. - Since the
FileManager
method can throw errors, you use ado...catch
block. - 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
map
to convert each name into a completeURL
with 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
URL
matching the row number. - Get the icon for this
URL
.NSWorkspace
is 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
FileManager
shared instance. - Use
do...catch
to 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
NSFileExtendedAttributes
key as it contains a messy dictionary that isn’t really useful. - Join these array entries into a single string & return it.
- If the
try
throws 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!