Simplify creating file URL in a SearchPathDirectory e.g. DocumentDirectory

Introduction
An application often needs to create file in and read file from a SearchPathDirectory e.g. current users Document directory. For this, we need to create a URL with file path pointing to the desired file.

However, At present there is no straight forward way to create such a URL in these search path directories.

This proposal is to add a function to FileManager to create a file path url.

Motivation: The Problem
Say, we want to save data to a document directory in a file named "data.dt".
At present, this URL can be created in the following way:

let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileURL = URL(fileURLWithPath: "data.dt", relativeTo: documentDirectory)
// or
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileURL = documentDirectory.appendingPathComponent("data.dt")

Probably this could be made more "friendly" :)

Proposed Solution

We can add a method to FileManager class with following signature. e.g.

func url(filePath: String, 
for directory: FileManager.SearchPathDirectory, 
in domainMask: FileManager.SearchPathDomainMask) -> URL?

Then, we can use like:

     // it will give full file URL in document directory, in current user domain    
    FileManager.default.url(filePath: "hello.txt")

    // or we can specify any search path directory and user domain mask
    FileManager.default.url(filePath: "hello.txt", for: .userDirectory) // Full file path in user home directory
    FileManager.default.url(filePath: "hello.txt", for: .documentDirectory, in: .userDomainMask) // pass as needed

Detailed Design:

a probable implementation could be like:

    @available(macOS 10.6, iOS 4.0, *)
    public func(filePath: String, in directory: FileManager.SearchPathDirectory = .documentDirectory, domain: FileManager.SearchPathDomainMask = .userDomainMask) -> URL?  {
            guard let directory = FileManager.default.urls(for: directory, in: domain).first else { return nil }
            return directory.appendingPathComponent(filePath)
        }

Source Compatibility
This change is additive only

Effect on API resilience
This change is additive only

Alternatives considers
Instead of that we could have constructor for URL like this:

let url = URL(fileName: "data.dt" in:.documentDirectory domain: .userDomainMask)

We can add a constructor to URL class as follows:

public init?(fileName: String, in directory: FileManager.SearchPathDirectory = .documentDirectory, domain: FileManager.SearchPathDomainMask = .userDomainMask) {
        guard let directory = FileManager.default.urls(for: directory, in: domain).first else { return nil }
        self.init(fileURLWithPath: fileName, relativeTo: directory)
    }

I'm not certain, but this seems very specific to Apple APIs, and thus may not be a good candidate for Swift.

This is also quite simple to implement in an extension.

This seems to be mixing concerns in a way that might belong in a utility library, but not the stdlib.

I'd also expect the method to let me specify a FileManager other than .default, and at that point you're really not saving much over the two separate calls.

Thanks for the quick feedback, now:

  1. I also agree, its very specific to Apple API. Yet I thought to pitch because Swift repo there are some apple specific api. e.g in FileManager class
    @available(macOS 10.6, iOS 4.0, *)
    public func replaceItemAt(_ originalItemURL: URL, withItemAt newItemURL: URL, backupItemName: String? = nil, options: FileManager.ItemReplacementOptions = ) throws -> URL? {

  2. True, it could be also a simple extension. Like many other additive proposals e.g. SE-0220

Hi, thanks for discussing the scenario. In that case, it can be a function in the FileManager. FileManager already has a method named url.

There could be another url function with following signature:

@available(OSX 10.6, *)
    func url(filePath: String, for directory: FileManager.SearchPathDirectory, in domainMask: FileManager.SearchPathDomainMask) -> URL?

What do you think?

Updating the idea/title:
instead of URL, proposing to add method in FileManager similar to existing FileManager.url method.

I've developed FilesProvider framework which abstracts these kind of operations. Working with relative urls are not easy. There are bugs that forces me to convert back these urls to absolute form. For example AirDrop won't work when you use UIActivityViewController to share a file in case you pass a relative file url to it..

hi, nice framework, thanks for sharing.

pass a relative file url to [...]

This proposal is not about relative path, its about a little addition to FileManager that can create full path.
Please have a look into the "Proposed Solution" paragraph.

1 Like

Its heritage is from the Apple API’s yes, but the general concept is far more useful when writing code for multiple platforms.

It is a lot of work to find out (and remember) the best place to be storing things on the different platforms. It is really nice to be able to essentially ask the operating system for a location based on the semantics of what it will be used for:

Application:
• “I need to write something to a file to pass it to a subprocess, but I want you to clean it up after me if you run out of batteries in the middle of what I’m doing. Where should I put it?”
• “I want to store something to speed up future work, but I can always do recreate it if you would prefer, so you are welcome to delete it if you need to make space. Where should I put it?”
• “I need a place to store this where the user can’t see it and won’t break it. Oh and it had better be there next time I run. Where should I put it?”

Operating Systam: “I store that stuff here.”

I have written a centralized extension in a package here, but it would be nice to have something similar directly in Foundation and benefiting from the expertise of the same people who port Swift to the other operating systems.

That said, I do not think such an API should actually use FileManager.SearchPathDirectory (and my extension does not). FileManager.SearchPathDirectory contains many locations which are specific to Apple platforms or have no semantic relevance. The user‐managed directories such as the Documents directory in the original post really should only be used if the user directs the application there from a Save or Open Dialog, in which case the application does not need to search for it itself. Speaking as a user, I detest it when applications write stuff into my directories without asking me.

3 Likes

I have written a centralized extension in a package here 6, [...]

IMHO, it would be great if Swift had a Util package with such goodies! Such stuff can really boost productivity and happiness of a developer :) :+1: