12

How can I expand a path String with a tilde in Swift? I have a string like "~/Desktop" and I'd like to use this path with the NSFileManager methods, which requires the tilde to be expanded to "/Users/<myuser>/Desktop".

(This question with a clear problem statement doesn't exist yet, this should be easily findable. Some similar but not satisfying questions are Can not make path to the file in Swift, Simple way to read local file using Swift?, Tilde-based Paths in Objective-C)

Community
  • 1
  • 1
Kametrixom
  • 13,471
  • 6
  • 43
  • 61

4 Answers4

30

Tilde expansion

Swift 1

"~/Desktop".stringByExpandingTildeInPath

Swift 2

NSString(string: "~/Desktop").stringByExpandingTildeInPath

Swift 3

NSString(string: "~/Desktop").expandingTildeInPath

Home Directory

Additionally you can get the home directory like this (returns a String/String?):

NSHomeDirectory()
NSHomeDirectoryForUser("<User>")

In Swift 3 and OS X 10.12 it's also possible to use this (returns a URL/URL?):

FileManager.default().homeDirectoryForCurrentUser
FileManager.default().homeDirectory(forUser: "<User>")

Edit: In Swift 3.1 this got changed to FileManager.default.homeDirectoryForCurrentUser

Kametrixom
  • 13,471
  • 6
  • 43
  • 61
  • +, especially for self-answering – user3441734 Jul 03 '16 at 18:41
  • `FileManager.default()` would be simply `FileManager.default` in Swift 3.1 – l --marc l Jan 19 '17 at 06:52
  • So, the Swift 3.1 "~/Desktop" equivalent could be `let homeDir = FileManager.default.homeDirectoryForCurrentUser` followed by `let desktopDir = homeDir.appendingPathComponent("Desktop", isDirectory: true)` – l --marc l Jan 19 '17 at 06:55
  • 1
    When using the path in a URL (like `URL(fileURLWithPath: "~/Desktop").path`), this results in `"/private/tmp/~/Desktop"` – Ben Leggiero Apr 25 '17 at 15:21
  • @NickK9 I am no longer using Swift and am therefore unable to provide updates regarding this. You can update my answer to provide an update – Kametrixom Feb 03 '18 at 22:51
3

Return string:

func expandingTildeInPath(_ path: String) -> String {
    return path.replacingOccurrences(of: "~", with: FileManager.default.homeDirectoryForCurrentUser.path)
}

Return URL:

func expandingTildeInPath(_ path: String) -> URL {
    return URL(fileURLWithPath: path.replacingOccurrences(of: "~", with: FileManager.default.homeDirectoryForCurrentUser.path))
}

If OS less than 10.12, replace

FileManager.default.homeDirectoryForCurrentUser

with

URL(fileURLWithPath: NSHomeDirectory()
WillC7
  • 61
  • 6
1

Here is a solution that does not depend on the NSString class and works with Swift 4:

func absURL ( _ path: String ) -> URL {
    guard path != "~" else {
        return FileManager.default.homeDirectoryForCurrentUser
    }
    guard path.hasPrefix("~/") else { return URL(fileURLWithPath: path)  }

    var relativePath = path
    relativePath.removeFirst(2)
    return URL(fileURLWithPath: relativePath,
        relativeTo: FileManager.default.homeDirectoryForCurrentUser
    )
}

func absPath ( _ path: String ) -> String {
    return absURL(path).path
}

Test code:

print("Path: \(absPath("~"))")
print("Path: \(absPath("/tmp/text.txt"))")
print("Path: \(absPath("~/Documents/text.txt"))")

The reason for splitting the code into two methods is that nowadays you rather want URLs when working with files and folders and not string paths (all new APIs use URLs for paths).

By the way, if you just want to know the absolute path of ~/Desktop or ~/Documents and similar folders, there's an even easier way for that:

let desktop = FileManager.default.urls(
    for: .desktopDirectory, in: .userDomainMask
)[0]
print("Desktop: \(desktop.path)")

let documents = FileManager.default.urls(
    for: .documentDirectory, in: .userDomainMask
)[0]
print("Documents: \(documents.path)")
Mecki
  • 106,869
  • 31
  • 201
  • 225
  • @Alexander In terms of NSURL `~` is problematic as all directories must end with `/` as indicator that they are directories (otherwise using such an URL as baseURL will have undesired effects for example) and not files E.g. `file:///var/xxx`, what is xxx? You can't know. Without further knowledge NSURL will assume it is a file but in case of `file:///var/xxx/` it knows that `xxx` is a directory and a trailing `/` is legal for all file APIs on Mac (including BSD and Cocoa). If you need to resolve just `~`, call `FileManager.default.homeDirectoryForCurrentUser` and make result an URL. – Mecki Jun 05 '18 at 17:40
  • Ah, that makes sense. That's a useful convention to uphold – Alexander Jun 05 '18 at 18:20
  • @Alexander I will update the code and just treat a plain `~` as a special case. I think that is easier than trying to find a solution that always works correctly. – Mecki Jun 05 '18 at 18:54
  • [`NSURL.standardizingPath`](https://developer.apple.com/documentation/foundation/nsurl/1414302-standardizingpath) should be able to handle all this correctly, but for some reason, wasn't imported into `URL`. – Alexander Jun 05 '18 at 19:37
1

Swift 4 Extension

public extension String {

    public var expandingTildeInPath: String {
        return NSString(string: self).expandingTildeInPath
    }

}
Brody Robertson
  • 8,107
  • 2
  • 40
  • 42