URL(fileURLWithPath:) behavior change in iOS 26 - Tilde (~) in filename causes unexpected path resolution

Environment:

  • Xcode 26
  • iOS 26
  • Also tested on iOS 18 (working correctly)

Description:

I'm experiencing a behavior change with URL(fileURLWithPath:) when the filename starts with a tilde (~) character.

On iOS 18, passing a filename like ~MyFile.txt to URL(fileURLWithPath:) treats the tilde as a literal character. However, on iOS 26, the same code resolves the tilde as the home directory, resulting in unexpected output.

Minimal Example:

  let filename = "~MyFile.txt"                                                                                                                                       
  let url = URL(fileURLWithPath: filename)                                                                                                                           
  print(url.lastPathComponent)                                                                                                                                       

Expected Result (iOS 18):
~MyFile.txt

Actual Result (iOS 26):
924AF0C4-C3CD-417A-9D5F-733FBB8FCF29

The tilde is being resolved to the app's container directory, and lastPathComponent returns the container UUID instead of the filename.

Questions:
1. Is this an intentional behavior change in iOS 26? 2. Is there documentation about this change? 3. What is the recommended approach for extracting filename components when the filename may contain special characters like ~?

Workaround:

Using NSString.lastPathComponent works correctly on both iOS versions:

 let filename = "~MyFile.txt"                                                                                                                                       
  let result = (filename as NSString).lastPathComponent                                                                                                              
  // Returns: "~MyFile.txt" ✅                                                                                                                                       

Is this the recommended approach going forward?

Answered by DTS Engineer in 873928022
Bug filed FB21757864

Thanks. I’ve added my own comments to your bug.

I want to understand whether this is a bug fix …

In my opinion this is a bug, but the resolution of your bug report will act as an official answer to that question (-:

We need to extract the filename without extension for display purposes.

Ah, I’ve found myself in that very conundrum. It’s annoying that we never got a Swift equivalent of Objective-C’s -stringByDeletingPathExtension method. I think it’d be reasonable for you to file an enhancement request for that. And if you do, please post that bug number as well.

The original code used:

Are you sure that’s the right code? Because it doesn’t use deletingPathExtension, which means it returns the name with the extension.

Regardless, given your requirements I agree that prepending a slash is a reasonable option. For example:

func fileNameWithoutExtension(_ fileNameWithExtension: String) -> String {
    URL(fileURLWithPath: "/" + fileNameWithExtension).deletingPathExtension().lastPathComponent
}

IMPORTANT This only makes sense when the file doesn’t exist on disk. If the file does exist on disk, it’s better to use the localizedNameKey resource value. That takes into account whether the user wants the extension hidden or not.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I won't believe this is an intentional change. Do you have a feedback report yet? If not, would you mind to file one and share your report ID here? Thanks.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

Solution - Adding leading slash with new URL API:

 func getFileName(from fileNameWithExtension: String) -> String {                                                                                                   
      let url = URL(                                                                                                                                                 
          filePath: "/" + fileNameWithExtension,                                                                                                                     
          directoryHint: .notDirectory,                                                                                                                              
          relativeTo: nil                                                                                                                                            
      )                                                                                                                                                              
                                                                                                                                                                     
      // Debug logging                                                                                                                                               
      print("Input: \(fileNameWithExtension)")                                                                                                                       
      print("URL created: \(url)")                                                                                                                                   
      print("URL.path: \(url.path)")                                                                                                                                 
      print("URL.lastPathComponent: \(url.lastPathComponent)")                                                                                                       
                                                                                                                                                                     
      return url.lastPathComponent                                                                                                                                   
  } 
// Usage                                                                                                                                                           
  let result = getFileName(from: "~TestFile.irfd") 

Debug Output (iOS 26):

Input: ~TestFile.irfd
URL created: file:///~TestFile.irfd
URL.path: /~TestFile.irfd
URL.lastPathComponent: ~TestFile.irfd

By prefixing with /, the filename becomes an absolute path, preventing the ~ from being interpreted as the home directory symbol.

This approach:

  • Uses the modern URL(filePath:directoryHint:relativeTo:) API (not deprecated)
  • Works correctly on both iOS 18 and iOS 26
  • Follows the "valid pathname" rule you mentioned

Btw, i could not find the official Apple documentation that explains this path resolution behavior for ~ and the new URL API. @DTS Engineer Thanks for suggestion. if you know about any link about functions above please, share it Thanks.

I’m gonna second Ziqiao’s advice here: You should file a bug about this. When you’re done, please post your bug number.


Solution - Adding leading slash with new URL API:

That’s not really a solution, in that it produces a different URL. ~TestFile.irfd should produce a relative URL, relative to the current working directory [1]. OTOH, /~TestFile.irfd produces an absolute URL. You can’t reasonably substitute one for the other. And if you’re doing that, it suggests that you’re not properly tracking the difference between a URL and a URL path component.

Honestly, creating a file URL from ~TestFile.irfd is weird on iOS, because you generally can’t assume anything useful about the current working directory [2]. So, how did you bump into this problem?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1l Which is typically / in an iOS app’s process.

[2] In contrast, on macOS it’s reasonable to do this, but usually only in the context of creating a command-line tool.

@DTS Engineer
Our iOS app receives filenames from a server (e.g., ~TestFile.irfd). We need to extract the filename without extension for display purposes. The original code used:


func getFileName(fileNameWithExtension: String) -> String {                                                                                                                                                                
      // Pure string manipulation - correct for filename handling                                                                                                                                                                       
       let fileName = URL(fileURLWithPath: fileNameWithExtension).lastPathComponent                                                                                                                                          
      return fileName                                                                                                                                                                                   
  }

This worked on iOS 18, but broke on iOS 26 when customers started using filenames beginning with ~.

We don't actually need a URL or path. We're doing pure string manipulation on a filename to extract components. We used URL(fileURLWithPath:) as a convenient way to get lastPathComponent, but that's
semantically wrong for our use case.

This will be right suitable approach for our case,

let fileName = (fileNameWithExtension as NSString).lastPathComponent

This is the right approach because:

  • We have a filename, not a path
  • We need string manipulation, not path resolution
  • NSString.lastPathComponent doesn't perform path resolution

Bug filed: FB21757864

@DTS Engineer

I'd like to clarify the expected behavior for URL(fileURLWithPath:) with a tilde-prefixed filename:

 let url = URL(fileURLWithPath: "~TestFile.irfd")                                                                                                                                                                                      
 print(url.lastPathComponent)  

Results:

  • iOS 18: Returns ~TestFile.irfd
  • iOS 26: Returns 924AF0C4-C3CD-417A-9D5F-733FBB8FCF29 (app sandbox UUID)

Is the iOS 26 behavior the correct/intended behavior? Was iOS 18 being lenient by not resolving ~ as the home directory?

I want to understand whether this is a bug fix, a behavior change, or something that should be reported.

Bug filed FB21757864

Thanks. I’ve added my own comments to your bug.

I want to understand whether this is a bug fix …

In my opinion this is a bug, but the resolution of your bug report will act as an official answer to that question (-:

We need to extract the filename without extension for display purposes.

Ah, I’ve found myself in that very conundrum. It’s annoying that we never got a Swift equivalent of Objective-C’s -stringByDeletingPathExtension method. I think it’d be reasonable for you to file an enhancement request for that. And if you do, please post that bug number as well.

The original code used:

Are you sure that’s the right code? Because it doesn’t use deletingPathExtension, which means it returns the name with the extension.

Regardless, given your requirements I agree that prepending a slash is a reasonable option. For example:

func fileNameWithoutExtension(_ fileNameWithExtension: String) -> String {
    URL(fileURLWithPath: "/" + fileNameWithExtension).deletingPathExtension().lastPathComponent
}

IMPORTANT This only makes sense when the file doesn’t exist on disk. If the file does exist on disk, it’s better to use the localizedNameKey resource value. That takes into account whether the user wants the extension hidden or not.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@DTS Engineer

Thank you for your detailed response and for adding comments to my bug report.

I have a few follow-up questions:

Thanks. I’ve added my own comments to your bug.

I tried to access FB21757864 via Feedback Assistant, but I don't see any replies yet. Are internal comments visible to the reporter, or are they only accessible to Apple engineers?

Ah, I’ve found myself in that very conundrum. It’s annoying that we never got a Swift equivalent of Objective-C’s -stringByDeletingPathExtension method. I think it’d be reasonable for you to file an enhancement request for that. And if you do, please post that bug number as well.

I found deletingpathextension in Swift. Is this the method you were referring to ?

Regardless, given your requirements I agree that prepending a slash is a reasonable option.

You mentioned that prepending a slash is a reasonable option. However, since our use case involves pure string manipulation (extracting filename and extension from a
server-provided filename string), would using NSString methods be the more semantically correct approach? For example:

let fileName = ((fileNameWithExtension as NSString).deletingPathExtension as NSString).lastPathComponent 
                                                                      
let fileExtension = (fileNameWithExtension as NSString).pathExtension

This avoids URL semantics entirely and treats the input as what it actually is—a filename string rather than a path.

I'd appreciate your guidance on whether this approach is recommended over the URL-based solution.

Are internal comments visible to the reporter … ?

Yes.

If the engineers working on the bug need to get in touch, they can do that via Feedback Assistant. But I don’t need to do that because I can just reply here (-: so my comments on the bug were for them, not you.

Is this the method you were referring to ?

Yes, but it’s only on NSString, not String. So this fails:

let s = "Hello Cruel World!.txt"
let s2 = s.deletingPathExtension
        // ^ Value of type 'String' has no member 'deletingPathExtension'

Instead you have to do this:

let s2 = (s as NSString).deletingPathExtension

All very suboptimal )-:

would using NSString methods be the more semantically correct approach?

*shrug*

This is definitely one of those engineering trade-offs we make every day:

  • I prefer staying away from NSString where I can.
  • But the alternative is URL, which isn’t great for this task.

Which is better? I see that as a matter of opinion.

The one thing that would definitely be better is a deletingPathExtension property on String, hence my suggestion that you file an ER for that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

URL(fileURLWithPath:) behavior change in iOS 26 - Tilde (~) in filename causes unexpected path resolution
 
 
Q