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 2 of 4 of this article. Click here to view the first page.

Contents

Hide contents

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.

FileManager Class File Information

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:

  1. Check that you can get a reference to the window, since that’s where the NSOpenPanel will be displayed.
  2. Create a new NSOpenPanel and set some properties to only permit a single selection which must be a folder.
  3. Display the NSOpenPanel modally in the window and use a closure to wait for the result.
  4. 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 specific ViewController property. For a quick temporary test, you print the selected URL 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.

Selecting a Folder

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:

  1. Get the FileManager class singleton, just as before.
  2. Since the FileManager method can throw errors, you use a do...catch block.
  3. Try to find the contents of the folder contentsOfDirectory(atPath:) and return an array of file and folder names inside.
  4. Process the returned array using map to convert each name into a complete URL with its parent folder. Then return the array.
  5. 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:

  1. Get the URL matching the row number.
  2. Get the icon for this URL. NSWorkspace is another useful singleton; this method returns the Finder icon for any URL.
  3. Get a reference to the cell for this table. The FileCell identifier was set in the Storyboard.
  4. If the cell exists, set its text field to show the file name and its image view to show the file icon.
  5. 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!

Show Folder Contents

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.

File Information

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:

  1. As usual, get a reference to the FileManager shared instance.
  2. Use do...catch to trap any errors.
  3. 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 String rawValue.
  4. 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.
  5. Join these array entries into a single string & return it.
  6. 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:

Folder Information

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