I have code in my macOS app that shows a save dialog using NSSavePanel. I want the panel to always open in a specific directory that I send as a parameter to my function. (e.g., /User/Desktop).
Here’s the simplified version of my code:
- (NSURL *)doSave:(NSURL *)initialLocation
withFileName:(NSString *)fileName
withTitle:(NSString *)title
{
NSSavePanel *_savePanel = [NSSavePanel savePanel];
// I want to force it to open here:
[_savePanel setDirectoryURL:initialLocation];
[_savePanel setNameFieldStringValue:fileName];
[_savePanel setTitle:title];
if ([_savePanel runModal] == NSModalResponseOK) {
return [_savePanel URL];
}
return nil;
}
On macOS Sonoma (14) and earlier, this worked: The dialog always opened at initialLocation.
But on macOS Sequoia (15), the panel always opens at the last location where a file was saved (system-wide), even though I set directoryURL.
I tried:
Clearing the identifier (_savePanel.identifier = nil)
Setting _savePanel.restorable = NO.
Clearing NSUserDefaults keys like NSNavLastRootDirectory.
Resetting directoryURL right before runModal.
But Sequoia still restores the last-used location, seemingly overriding directoryURL.
How can I force NSSavePanel to honour setDirectoryURL: on Sequoia (macOS 15)? Did Apple change the expected way to set the initial directory? Is there a new API or flag to disable the "last-used folder" behaviour?
Additional observation (LLDB):
// Right after creating the save panel:
(lldb) po [_savePanel directoryURL]
file:///User/Documents/ // Last used folder
// After calling setDirectoryURL:initialLocation:
(lldb) po [_savePanel directoryURL]
nil // My assignment was wiped out
I found out that if I use the old API setDirectory: (which is deprecated), the panel does open in the folder we specify and doesn’t get overridden by the system. But I can't rely on a deprecated API.
setDirectory:isn't deprecated API, it's just replaced by a different notation. Have you tried building on macOS 15? Post a minimal reproducible example please.setDirectory:andsetDirectoryURL:.setDirectoryURL:is replaced by propertydirectoryURLbut does the same.