macOS Sequoia(15)下如何让NSSavePanel遵守setDirectoryURL设置?
我的macOS应用通过NSSavePanel实现保存对话框,期望面板始终打开在函数参数指定的目录(如/User/Desktop)。简化代码如下:
- (NSURL *)doSave:(NSURL *)initialLocation withFileName:(NSString *)fileName withTitle:(NSString *)title { NSSavePanel *_savePanel = [NSSavePanel savePanel]; // 期望强制打开到此目录 [_savePanel setDirectoryURL:initialLocation]; [_savePanel setNameFieldStringValue:fileName]; [_savePanel setTitle:title]; if ([_savePanel runModal] == NSModalResponseOK) { return [_savePanel URL]; } return nil; }
在macOS Sonoma(14)及更早系统中,该代码可正常让对话框打开在initialLocation指定目录,但在macOS Sequoia(15)下,面板始终打开系统全局的上次文件保存位置,完全无视setDirectoryURL的设置。
我尝试过以下方法,但均无效:
- 清除identifier(
_savePanel.identifier = nil) - 设置
_savePanel.restorable = NO - 清除
NSUserDefaults中如NSNavLastRootDirectory的键值 - 在
runModal前重新设置directoryURL
通过LLDB调试发现:创建保存面板后,directoryURL为上次使用的文件夹;调用setDirectoryURL:initialLocation后,directoryURL直接变为nil。另外,使用已废弃的旧APIsetDirectory:时,面板可正常打开指定目录且不被系统覆盖,但显然不能依赖废弃API。
请问如何在macOS Sequoia(15)下强制NSSavePanel遵守setDirectoryURL设置?Apple是否更改了设置初始目录的方式?是否有新API或标志可禁用「上次使用文件夹」行为?
解决方案
在macOS Sequoia中,Apple对NSSavePanel的目录恢复逻辑做了调整,可通过以下几种有效方式强制指定初始目录:
调整代码顺序:先设置允许的文件类型,再指定目录URL
Sequoia中,若未先设置allowedFileTypes,setDirectoryURL的设置可能被系统默认逻辑覆盖。调整代码顺序即可解决:- (NSURL *)doSave:(NSURL *)initialLocation withFileName:(NSString *)fileName withTitle:(NSString *)title { NSSavePanel *_savePanel = [NSSavePanel savePanel]; // 先设置允许的文件类型(根据业务场景调整,比如@"txt"、@"pdf") [_savePanel setAllowedFileTypes:@[@"your-target-file-type"]]; // 再指定初始目录 [_savePanel setDirectoryURL:initialLocation]; [_savePanel setNameFieldStringValue:fileName]; [_savePanel setTitle:title]; [_savePanel setRestorable:NO]; [_savePanel setIdentifier:nil]; if ([_savePanel runModal] == NSModalResponseOK) { return [_savePanel URL]; } return nil; }使用异步Sheet模式替代同步runModal
部分开发者反馈,使用异步的Sheet展示方式时,系统的目录恢复逻辑不会干扰指定的初始目录。如果应用适配Sheet模式,可尝试这种方式:- (void)doSaveAsync:(NSURL *)initialLocation withFileName:(NSString *)fileName withTitle:(NSString *)title completion:(void(^)(NSURL *))completion { NSSavePanel *_savePanel = [NSSavePanel savePanel]; [_savePanel setDirectoryURL:initialLocation]; [_savePanel setNameFieldStringValue:fileName]; [_savePanel setTitle:title]; [_savePanel setRestorable:NO]; [_savePanel setIdentifier:nil]; [_savePanel beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSModalResponse result) { if (result == NSModalResponseOK) { completion([_savePanel URL]); } else { completion(nil); } }]; }提前校验初始目录的有效性
Sequoia对无效目录URL的处理更严格,若initialLocation不存在或不可访问,会直接 fallback 到上次保存位置。可提前添加校验逻辑:BOOL isDir = NO; if (![[NSFileManager defaultManager] fileExistsAtPath:[initialLocation path] isDirectory:&isDir] || !isDir) { // 处理无效目录,比如 fallback 到用户桌面 initialLocation = [NSURL fileURLWithPath:NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES).firstObject]; }
内容的提问来源于stack exchange,提问作者Ishi

