肇鑫的技术博客

业精于勤,荒于嬉

第三方应用使用DocumentGroup时,模仿Pages的工具栏

最近,我在开发iOS的Markdown应用的时候遇到一个问题,应用是使用DocumentGroup开发的,但是默认会显示标题,从而导致留给工具栏的空间太小,工具栏的图标经常被折叠起来。

IMG_1181

我一开始只是想把后退去掉,换成三个点的菜单,然后去掉标题。结果发现后退去不掉。于是我就想看看苹果自己是如何做的。于是我打开了苹果自己的Notes和Numbers。不过Notes其实是有内部库的,于是主要参考的Numbers。

IMG_1184

我把Numbers截图给AI,说想要弄一个这个风格的。结果AI搞不定。它虽然设置了标题为空,但是标题始终显示。于是我有把这个描述给Grok,Grok说,这是因为DocumentGroup自动包含了一层Navigation,你需要将它禁用。结果我禁用了,还是不行。又去问Grok,它又说,这是因为iOS的某些版本,有bug,不执行这个操作。但是网友总结了三种办法,1,2,3,然后挨个试。结果第一种就可以了。

这个是我应用最终的效果。

IMG_1205

最核心的代码只需要如下的部分。

截屏2026-04-12 10.50.33

Swift Package应用签名与公证实战总结

问题背景

fork 了一个第三方 macOS 应用,原 release 版本未签名,但提供了源码,为Swift Package格式,需要重新签名并公证后分发。


遇到的坑

1. Swift Package Manager 无法启用 Hardened Runtime

问题:使用 swift build 构建的可执行文件,无法通过签名添加 hardened runtime。公证时一直报错:

The executable does not have the hardened runtime enabled.

原因:Swift Package Manager 构建时不会启用 hardened runtime,签名时添加也不被认可。

解决:使用 XcodeGen 生成 Xcode 项目,在项目配置中启用。

2. Apple Development 证书无法公证

问题:最初只有 Apple Development 证书(用于开发调试),公证时报错:

The binary is not signed with a valid Developer ID certificate.

原因:公证必须使用 Developer ID Application 证书,不能用 Apple Development。

解决:在 Apple Developer 账号中添加 Developer ID Application 证书。

3. 时间戳问题

问题:签名缺少安全时间戳:

The signature does not include a secure timestamp.

解决:在签名配置中添加 --timestamp 参数。

4. get-task-allow entitlement 问题

问题:分发版本包含开发用 entitlement:

The executable requests the com.apple.security.get-task-allow entitlement.

解决:在 entitlements 配置中明确设置 com.apple.security.get-task-allow: false


经验教训

证书类型

证书类型 用途
Apple Development 开发调试,不能公证
Developer ID Application 分发和公证 ✅

签名配置要点

  1. Hardened Runtime - 必须启用
  2. 时间戳 - 必须添加
  3. entitlements - 分发版本禁止包含 get-task-allow
  4. 使用 Manual 签名 - 避免自动签名冲突

最佳实践(简单有效)

1. 使用 Xcode 项目而非 SPM

# 安装 XcodeGen
brew install xcodegen

# 配置 project.yml(关键设置)
targets:
  YourApp:
    settings:
      base:
        ENABLE_HARDENED_RUNTIME: YES
        CODE_SIGN_STYLE: Manual
        CODE_SIGN_IDENTITY: "Developer ID Application: Your Name (TEAM_ID)"
        OTHER_CODE_SIGN_FLAGS: "--timestamp"
    entitlements:
      properties:
        com.apple.security.get-task-allow: false

# 生成项目
xcodegen generate

# 构建
xcodebuild -project YourApp.xcodeproj -scheme YourApp -configuration Release build

2. 验证签名

codesign -dvv YourApp.app/Contents/MacOS/YourApp

确认输出包含:

  • flags=0x10000(runtime) - hardened runtime 已启用
  • Timestamp=... - 有时间戳

3. 公证流程

# 1. 创建 zip
zip -r YourApp.zip YourApp.app

# 2. 提交公证(需要 App 专用密码)
xcrun notarytool submit YourApp.zip \
  --apple-id "your@email.com" \
  --password "app-specific-password" \
  --team-id "TEAM_ID"

# 3. 等待完成
xcrun notarytool wait <submission-id> --apple-id "your@email.com" --password "password" --team-id "TEAM_ID"

# 4. 附加票据
xcrun stapler staple YourApp.app

总结

最简单有效的方式

  1. XcodeGen 生成 Xcode 项目(不要用纯 SPM)
  2. project.yml 中配置好签名和 hardened runtime
  3. xcodebuild 构建
  4. notarytool 公证

不要试图用 swift build + 手动签名的方式来处理需要公证的分发版本,SPM 的构建产物不兼容。

NavigationSplitView的一些奇技淫巧

Sidebar按钮隐藏起来的正确做法

使用

.toolbar(removing: .sidebarToggle)

可以隐藏侧边栏切换按钮。但是注意,这个不是放在作用在NavigationSplitView上面的。而不是作用在sidebar的视图上面。即必须按照如下的方式使用:

NavigationSplitView(columnVisibility: $columnVisibility) {
    sidebar
        .toolbar(removing: .sidebarToggle)
    } content: {
    content
    } detail: {
    detail
}

隐藏Sidebar的正确做法

NavigationSplitView是三栏的,但是如果你要使用两栏,则有两种方式。

使用sidebar

使用sidebar的好处是,sidebar可以隐藏,也可以显示。
使用sidebar+detail的方式。

不使用sidebar

如果你希望永远显示为两栏,不希望隐藏任何一个栏隐藏,那么使用此方式。

NavigationSplitView(columnVisibility: .constant(.doubleColumn)) {
    EmptyView()
        .toolbar(removing: .sidebarToggle)
    } content: {
    content
    } detail: {
    detail
}

这个的重点在于,通过sidebar设置隐藏了切换sidebar按钮。同时,选择两栏模式,默认不显示sidebar。这样sidebar就无法被调用出来了。并且一直是两栏。

配合Settings使用

在使用Settings时候,包含sidebar的NavigationSplitView的sidebar按钮会导致出现问题。因此,必须使用上面的第二种方式,隐藏sidebar,并且使用一直存在的两栏。