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
More Features
The app is getting better, but it’s still missing a few things:
- Clicking on Show Invisible Files doesn’t change anything.
- Double-clicking on a folder should drill into its contents.
- The Move Up button needs to move back up the folder hierarchy.
- Save Info should record the selected file’s details to a file.
You’ll tackle these next.
Handling Invisible Files
In Unix systems, files and folders whose name starts with a period are invisible. You’ll add code to handle this case.
Go to contentsOf(folder:)
and replace the line containing map
with the following:
let urls = contents
.filter { return showInvisibles ? true : $0.characters.first != "." }
.map { return folder.appendingPathComponent($0) }
The above adds a filter
that rejects hidden items if the showInvisibles
property is not true
. Otherwise the filter
returns every item, including hidden items.
Find the toggleShowInvisibles
method of ViewController
and insert this into the function:
// 1
showInvisibles = (sender.state == NSOnState)
// 2
if let selectedFolder = selectedFolder {
filesList = contentsOf(folder: selectedFolder)
selectedItem = nil
tableView.reloadData()
}
Here is what this code does:
- Sets the
showInvisibles
property based on the sender’s state. Since the sender is anNSButton
, it has eitherNSOnState
orNSOffState
. Because this is a checkbox button,NSOnState
means checked. - If there is a currently
selectedFolder
, regenerate thefilesList
and update the UI.
Build and run, select a folder and check and un-check the Show Invisible Files button. Depending on the folder you’re viewing, you may see files starting with a period when Show Invisible Files is checked.
Handling Double-Clicking on a Folder
In the storyboard, the table view has been assigned a doubleAction
that calls tableViewDoubleClicked
. Find tableViewDoubleClicked
and replace it with the following:
@IBAction func tableViewDoubleClicked(_ sender: Any) {
// 1
if tableView.selectedRow < 0 { return }
// 2
let selectedItem = filesList[tableView.selectedRow]
// 3
if selectedItem.hasDirectoryPath {
selectedFolder = selectedItem
}
}
Taking the above code comment-by-comment:
- Check to see whether the double-click occurred on a populated row. Clicking in a blank part of the table sets the
tableView's
selectedRow to -1. - Get the matching
URL
fromfilesList
. - If the
URL
is a folder, set theViewController's
selectedFolder
property. Just like when you select a folder using the Select Folder button, setting this property triggers the property observer to read the contents of the folder and update the UI. If theURL
is not a folder, nothing happens.
Build and run, select a folder containing other folders, and then double-click a folder in the list to drill down into it.
Handle the Move Up Button
Once you have implemented double-click to drill down, the next obvious step is to move back up the tree.
Find the empty moveUpClicked
method and replace it with the following:
@IBAction func moveUpClicked(_ sender: Any) {
if selectedFolder?.path == "/" { return }
selectedFolder = selectedFolder?.deletingLastPathComponent()
}
This first checks to see whether the selectedFolder
is the root folder. If so, you can’t go any higher. If not, use a URL
method to strip the last segment off the URL. Editing selectedFolder
will trigger the update as before.
Build and run again; confirm that you can select a folder, double-click to move down into a sub-folder and click Move Up to go back up the folder hierarchy. You can move up even before double-clicking a folder, as long as you are not already at the root level.
didSet
) can be incredibly useful. All the code for updating the display is in an observer, so no matter what method or UI element changes an observed property, the update happens with no need to do anything else. Sweet!Saving Information
There are two main ways to save data: user-initiated saves and automatic saves. For user-initiated saves, your app should prompt the user for a location to save the data, then write the data to that location. For automatic saves, the app has to figure out where to save the data.
In this section, you are going to handle the case when the user clicks the Save Info button to initiate a save.
You used NSOpenPanel
to prompt the user to select a folder. This time, you are going to use NSSavePanel
. Both NSOpenPanel
and NSSavePanel
are subclasses of NSPanel
, so they have a lot in common.
Replace the empty saveInfoClicked
method with the following:
@IBAction func saveInfoClicked(_ sender: Any) {
// 1
guard let window = view.window else { return }
guard let selectedItem = selectedItem else { return }
// 2
let panel = NSSavePanel()
// 3
panel.directoryURL = FileManager.default.homeDirectoryForCurrentUser
// 4
panel.nameFieldStringValue = selectedItem
.deletingPathExtension()
.appendingPathExtension("fs.txt")
.lastPathComponent
// 5
panel.beginSheetModal(for: window) { (result) in
if result == NSFileHandlingPanelOKButton,
let url = panel.url {
// 6
do {
let infoAsText = self.infoAbout(url: selectedItem)
try infoAsText.write(to: url, atomically: true, encoding: .utf8)
} catch {
self.showErrorDialogIn(window: window,
title: "Unable to save file",
message: error.localizedDescription)
}
}
}
}
Taking each numbered comment in turn:
- Confirm that everything you need is available: a window for displaying the panel and the
URL
whose info you are going to save. - Create an
NSSavePanel
. - Set the
directoryURL
property which dictates the initial folder shown in the panel. - Set the
nameFieldStringValue
property to supply a default name of the file. - Show the panel and wait in a closure for the user to finish.
- If the user selects a valid path for the data file (a valid
URL
) and clicks the OK button, get the file information and write it to the selected file. If there is an error, show a dialog. Note that if the user clicks Cancel on the save dialog, you simply ignore the operation.
write(to:atomically:encoding)
is a String method that writes the string to the provided URL
. The atomically
option means that the string will be written to a temporary file and then renamed, ensuring that you won’t end up with a corrupt file — even if the system crashes during the write. The encoding for the text in this file is set to UTF8, which is a commonly used standard.
Build and run, select a file or folder from the table and click Save Info. Select a save location, and click Save. You will end up with a text file that looks similar to the following:
NSSavePanel
is that if you try to overwrite a file that already exists, your app will automatically display a confirmation dialog asking if you want to replace that file.That closes off the list of features for this app, but there is one more feature I think would be a nice addition: recording the selected folder and item so that when the app restarts, the last selected folder is re-displayed.