{"id":53579,"date":"2026-10-20T15:24:49","date_gmt":"2026-10-20T19:24:49","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=53579"},"modified":"2026-10-20T15:24:49","modified_gmt":"2026-10-20T19:24:49","slug":"getting-a-file-path-from-a-swift-url","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2026\/10\/20\/getting-a-file-path-from-a-swift-url\/","title":{"rendered":"Getting a File Path From a Swift URL"},"content":{"rendered":"<p><a href=\"https:\/\/forums.swift.org\/t\/url-path-vs-path-percentencoded-for-file-urls\/75358\">Seth Willits<\/a> (2024, <a href=\"https:\/\/github.com\/swiftlang\/swift-foundation\/issues\/1011\">bug<\/a>):<\/p>\n<blockquote cite=\"https:\/\/forums.swift.org\/t\/url-path-vs-path-percentencoded-for-file-urls\/75358\">\n<p>There&rsquo;s a deprecation warning in Xcode for <code>URL.path<\/code> that reads:<\/p>\n<blockquote>\n<p>WARNING: <code>path<\/code> will be deprecated in a future version of macOS: Use <code>path(percentEncoded:)<\/code> instead<\/p>\n<\/blockquote>\n<p>The problem is something like this:<\/p>\n<pre>Bundle.main.url(forAuxiliaryExecutable: \"tool\")!.path<\/pre>\n<p>&#8230; which returns the expected file path (ex: <code>\/Applications\/MyApp.app\/Contents\/tool<\/code>)<\/p>\n<p>would be quickly rewritten <em>as suggested<\/em> to:<\/p>\n<pre>Bundle.main.url(forAuxiliaryExecutable: \"tool\")!.path(percentEncoded: false)<\/pre>\n<p>&#8230; which simply returns <tt>tool<\/tt>, because the URL returned is relative to a base URL.<\/p>\n<p>[&#8230;]<\/p>\n<p>I&rsquo;ve always found it not-completely-obvious to newcomers how to get a standard file path string from a file URL and this only makes it <em>more<\/em> confusing.<\/p>\n<\/blockquote>\n\n<p>There are at least three issues here:<\/p>\n\n<ol>\n\n<li><p><code>url.path<\/code> returns an absolute path, but <code>url.path(percentEncoded: false)<\/code> returned a relative path as of his writing. I tested this on macOS 26, and now it returns an absolute path. The documentation now says, &ldquo;This function will resolve against the base <code>URL<\/code>.&rdquo; It seems the behavior of the method changed without it being documented when, so be careful if you&rsquo;re deploying to a mix of OS versions.<\/p><\/li>\n\n<li><p><code>percentEncoded<\/code> has a default value of <code>true<\/code>, whereas <code>path<\/code> did not return an encoded string. In other words, <code>url.path()<\/code> and <code>url.path<\/code> are completely different. So this is <a href=\"https:\/\/mjtsai.com\/blog\/2026\/09\/18\/spamsieve-3-3-2\/\">another case<\/a> where Apple updated the API but flipped the default&mdash;a good way to introduce bugs.<\/p><\/li>\n\n<li><p>It&rsquo;s not obvious how to get the full file system path out of a <code>URL<\/code>. With <code>NSURL<\/code>, <code>url.path<\/code> was natural and did the right thing. Now, the <code>URL<\/code> equivalent is deprecated.<\/p>\n<p>The natural replacement, <code>url.path()<\/code>, is wrong. So is Xcode&rsquo;s recommendation of <code>url.(percentEncoded: false)<\/code>.<\/p>\n<p><code>url.absoluteURL.path(percentEncoded: false)<\/code> is correct but verbose.<\/p>\n<p><code>url.absoluteURL.standardizedFileURL.path(percentEncoded: false)<\/code> is possibly better but even more verbose.<\/p>\n<p><code>FilePath(url)?.string<\/code> uses a newer API but gives you an optional, even though for a file URL it should never fail.<\/p>\n<p>This seems like such basic functionality, but there&rsquo;s no API that&rsquo;s the obvious right choice, concise, and ergonomic.<\/p>\n<\/li>\n<\/ol>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/08\/02\/the-switch-from-file-paths-to-urls\/\">The Switch From File Paths to URLs<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/06\/30\/swift-url-improvements-at-wwdc-2022\/\">Swift URL Improvements at WWDC 2022<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2021\/12\/15\/proposed-foundation-url-improvements\/\">Proposed Foundation URL Improvements<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2013\/07\/13\/url-path-retrieval-cheat-sheet\/\">URL Path Retrieval Cheat Sheet<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2012\/12\/11\/nsurl-path-handling\/\">NSURL Path Handling<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Seth Willits (2024, bug): There&rsquo;s a deprecation warning in Xcode for URL.path that reads: WARNING: path will be deprecated in a future version of macOS: Use path(percentEncoded:) instead The problem is something like this: Bundle.main.url(forAuxiliaryExecutable: \"tool\")!.path &#8230; which returns the expected file path (ex: \/Applications\/MyApp.app\/Contents\/tool) would be quickly rewritten as suggested to: Bundle.main.url(forAuxiliaryExecutable: \"tool\")!.path(percentEncoded: false) [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2026-10-20T19:24:54Z","apple_news_api_id":"259f2ab1-4f66-44fe-90ba-9b4f121a382c","apple_news_api_modified_at":"2026-10-20T19:24:54Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/AJZ8qsU9mRP6QuptPEho4LA","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2887,30,2784,71,901,489],"class_list":["post-53579","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-27","tag-mac","tag-macos-27","tag-programming","tag-swift-programming-language","tag-url"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/53579","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=53579"}],"version-history":[{"count":1,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/53579\/revisions"}],"predecessor-version":[{"id":53580,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/53579\/revisions\/53580"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=53579"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=53579"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=53579"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}