SwiftUI macOS Menu 自定义标签只渲染第一个 Text
给 macOS 原生工具栏做一个「分支切换 chip」,设计稿是图标 + 分支名 + 下拉箭头三段横排。用 Menu + .menuStyle(.borderlessButton) 实现,label: 里写了一个 HStack { Text("⎇"); Text(branch); Text("▾") }——看起来非常自然,运行却只显示 ⎇,后面两段文字一片空白。
调了半天数据绑定,branch 值是对的,在旁边放一个普通 Text(branch) 完全正常。那为什么放进 Menu 的 label 就哑火?
现象
Menu {
// 菜单选项 ...
} label: {
HStack(spacing: 4) {
Text("⎇")
Text(branch) // 只有这个会被渲染
Text("▾") // 静默消失
}
.font(.system(size: 12, design: .monospaced))
.padding(.horizontal, 9).padding(.vertical, 3)
.background(...)
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.fixedSize()运行结果:chip 里只出现 ⎇,branch 和 ▾ 完全不可见,但同一个 branch 绑定到侧边栏的 Text(branch) 却正常显示。
根因
.menuStyle(.borderlessButton) 的 Menu 在 macOS 上渲染自定义 label 时,只会合成 label 视图树中的第一个 Text 节点,后续 Text 子视图静默丢弃。这是 AppKit/SwiftUI 桥接层在处理 borderlessButton 样式时的一个已知渲染限制,与数据绑定无关。
把多个 Text 放进 Menu { } label: { HStack { Text(...); Text(...); Text(...) } } 并搭配 .menuStyle(.borderlessButton) 时,第 2 个及以后的 Text 子视图会被静默吞掉,不报错、不警告、数据也是对的,表现完全像数据 bug。
.fixedSize() / .menuIndicator(.hidden) 对此没有任何影响,加不加都一样。
正解
有两种修法,按需选择:
方案 A:合并成单个 Text(快速修复)
把三段文字用字符串插值合并成一个 Text,失去独立样式控制,但 99% 的 chip 场景够用:
} label: {
Text("⎇ \(branch) ▾")
.font(.system(size: 12, design: .monospaced))
.foregroundStyle(Color.accentColor)
.lineLimit(1)
.padding(.horizontal, 9).padding(.vertical, 3)
.background(...)
}方案 B:换成 Button + .popover(完整控制)
Button 的 label 对多 Text HStack 没有任何限制,这个 bug 是 Menu 专属的。换成 Button + 自定义 .popover 还能彻底解锁菜单行样式(双列布局、右对齐标签、激活高亮等),这些是系统 Menu 的菜单项无法做到的:
@State private var isOpen = false
Button { isOpen.toggle() } label: {
HStack(spacing: 4) {
Text("⎇")
Text(branch)
Text("▾")
}
.font(.system(size: 12, design: .monospaced))
}
.buttonStyle(.plain)
.popover(isPresented: $isOpen, arrowEdge: .bottom) {
// 自定义下拉行:名称左对齐,状态标签右对齐,当前项高亮
VStack(alignment: .leading, spacing: 0) {
ForEach(branches) { b in
BranchRow(branch: b, isCurrent: b.name == branch)
.onTapGesture { branch = b.name; isOpen = false }
}
}
.padding(6)
}- 只需展示文字 chip:把所有段拼成一个
Text("⎇ \(branch) ▾")即可。 - 需要自定义下拉行样式:用
Button { } label: { HStack { ... } }+.popover,完全绕开Menu的渲染限制,同时获得完整的行样式控制权。
一句话外卖
SwiftUI macOS
Menu+.menuStyle(.borderlessButton)的 label 只能放单个Text,多TextHStack 会静默截断——想要多段文字或自定义下拉行,换成Button+.popover。