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.

3.5 (11) · 2 Reviews

Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Contents

Hide contents

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:

  1. Sets the showInvisibles property based on the sender’s state. Since the sender is an NSButton, it has either NSOnState or NSOffState. Because this is a checkbox button, NSOnState means checked.
  2. If there is a currently selectedFolder, regenerate the filesList 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.

Show Invisible Files

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:

  1. 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.
  2. Get the matching URL from filesList.
  3. If the URL is a folder, set the ViewController'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 the URL 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.

Move Up The Folder Tree

Note: As you've seen, using property observers (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:

  1. Confirm that everything you need is available: a window for displaying the panel and the URL whose info you are going to save.
  2. Create an NSSavePanel.
  3. Set the directoryURL property which dictates the initial folder shown in the panel.
  4. Set the nameFieldStringValue property to supply a default name of the file.
  5. Show the panel and wait in a closure for the user to finish.
  6. 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:

File Info

Note: One neat feature of using 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.