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
}
}