跳到正文
返回博客
·约 1 分钟读完

Mac 应用 Guideline 4 拒审:关掉主窗口后没法再打开

Mac 应用关掉主窗口后,如果菜单里没有任何一项能把它找回来,Apple 会按 Guideline 4(设计)拒审。SwiftUI 和 AppKit 应用为什么会落到这一步、Apple 建议的两种改法,以及一个 30 秒自测。

这种拒审只会发生在 Mac 上。应用功能都正常,审核员把窗口关掉了:应用还在运行,菜单栏也还在,可菜单里没有任何一项能把窗口叫回来。我们自己的 Mac 小工具就收到过这条 Guideline 4(Design) 拒审,所以提交 Mac 版之前,值得亲手关一次窗口,找找回去的路。

Apple 怎么说

Apple 的原文是:"when the user closes the main application window there is no menu item to re-open it"(用户关掉主窗口后,没有菜单项可以重新打开它)。Apple 给了两条出路:

  • "implement a Window menu that lists the main window so it can be reopened, or provide similar functionality in another menu item"(实现一个列出主窗口的“窗口”菜单,让它能被重新打开,或在别的菜单项里提供类似功能);
  • "if the application is a single-window app, it might be appropriate to save data and quit the app when the main window is closed"(如果是单窗口应用,可以在主窗口关闭时保存数据并退出)。

为什么会这样

SwiftUI。 主场景用的是 WindowGroup 时,Apple 文档写明应用 "continues to run even after closing all of its windows"(关掉所有窗口后仍继续运行)。“窗口”菜单只列出已打开的窗口,⌘W 之后它就从菜单里消失了。菜单里唯一的回路是“文件 > 新建”,它会从这个窗口组新开一个窗口;而有些单窗口应用觉得“新建”没意义,用 CommandGroup(replacing: .newItem) { } 把它删了,于是一条菜单回路都不剩。

AppKit。 除非 app delegate 在 applicationShouldTerminateAfterLastWindowClosed(_:) 里返回 true,否则最后一个窗口关掉后应用会继续运行。如果菜单里没有东西能把窗口叫回来,就是同样的死胡同。如果窗口是你在 Swift 代码里自己创建的,还要把 isReleasedWhenClosed 设为 false(Apple 文档说明 Swift 和 ARC 代码需要这么做;NSWindow 默认是 true),并持有一个强引用,这样同一个窗口才能再次显示。

怎么改

SwiftUI 单窗口应用:改用 Window 场景。 Apple 文档说,“窗口”菜单本身就会列出这类单例窗口;并且 "if your app uses a single window as its primary scene, the app quits when the window closes"(如果应用以单个窗口作为主场景,关闭窗口时应用会退出)。这一改同时满足了 Apple 的两条建议。注意关窗口就等于退出,所以用户的改动要随时保存。

@main
struct NotesApp: App {
    var body: some Scene {
        Window("Notes", id: "main") {
            ContentView()
        }
    }
}

SwiftUI 保留 WindowGroup、关窗即退出: 用 @NSApplicationDelegateAdaptor 接一个 app delegate,在 applicationShouldTerminateAfterLastWindowClosed(_:) 里返回 true。或者干脆保留“文件 > 新建”,不要把 .newItem 替换成空。

AppKit: 要么保存后关窗即退出:

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
    true
}

要么让窗口一直存活,并给它一条回路。在“窗口”菜单里加一项,动作里调用 mainWindow.makeKeyAndOrderFront(nil)(比如用 NSApp.windowsMenu?.addItem(withTitle:action:keyEquivalent:) 添加),同时处理点击 Dock 图标:

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
    if !flag { mainWindow.makeKeyAndOrderFront(nil) }
    return true
}

应用已在运行时,用户点 Dock 图标或在访达里再次双击应用,AppKit 就会调用这个方法。点 Dock 能找回窗口只是锦上添花;Apple 的原文要的是菜单项,不要只交一个 Dock 处理就去重提。

30 秒自测

启动应用,按 ⌘W,然后只用菜单栏找回窗口:“窗口”菜单、“文件”菜单、应用菜单都看一遍。要么应用已经退出(而且改动已保存),要么某个菜单项能重新打开窗口。两样都不是,审核员也会发现同样的问题。最后再点一下 Dock 图标,确认窗口能回来。

Mac 独有的其他规则见 2.4.5:沙盒存文件与更新检查。更完整的上架前检查见 2.1 应用完整性 和 工具箱页面。

以上来自 Obelisk Club 自己的送审记录。Apple 的措辞和 API 会变,请以当前 App Review 指南和 Apple 文档为准。

作者

独立开发者,在 Obelisk Club 做本地优先的 Mac 与 iOS 效率应用。 关于作者 →