read

This Swift API is one of the most confusing to use. Not so long ago I discovered that URL inits for file can result in different behaviours.

This post will cover the other common operations concerning files and directories.

Is this a directory?

This very common operation to know if a URL is a file or a directory is not as easy as you think. In other languages (even very old ones), there is usually a isDirectory().

But the Swift answer is:

var isDir: ObjCBool = false
guard fileManager.fileExists(atPath: someURL.path, isDirectory: &isDir) else { return }
if isDir.boolValue {
    // This is a directory
}

Yes, it is the only way, using UnsafeMutablePointer<ObjCBool>.

I use a convenient extension that works on URL or path string, and try to forget the above un-swifty way..

public extension FileManager {
    func isDirectory(at url: URL) -> Bool {
        isDirectory(atPath: url.path)
    }

    func isDirectory(atPath: String) -> Bool {
        var isDirectory: ObjCBool = false
        let exists = fileExists(atPath: atPath, isDirectory: &isDirectory)
        return exists && isDirectory.boolValue
    }
}

You might have notice, FileManager uses at for URL and atPath for String paths. I’ll just stick to their convention.

File path & URL

So let’s get this out of the way. Many of the methods support 1. String path and 2. URL.

A path can even be relative eg. ../Taylor.swift

Creating a file URL MUST use the URL(fileURLWithPath: "") initializer. Other initializers /2020/02/09/pitfall-url-for-local-files/.

Read & Write String to file

// Read
let content = try String(contentsOfFile: filePath)

// Write
try newContent.write(toFile: filePath, atomically: true, encoding: .utf8)

There are also corresponding methods that takes in a fileURL.

Read & Write Data

Like for Data, there’s only the fileURL methods.

// Read
let content = try Data(contentsOf: fileURL)

// Write
try newContent.write(to: fileURL)

Traverse a directory

You can use the enumerator. As an example, the following is an example to traverse and find all the swift files in a directory, including the subdirectories.

let enumerator = fileManager.enumerator(at: directoryURL, includingPropertiesForKeys: nil)
while let url = enumerator?.nextObject() as? URL {
    if url.pathExtension == "swift" {
        // Handle the file url
    }
}

Image

@samwize

¯\_(ツ)_/¯

Back to Home