在选择嵌套的Picker时出现的问题
我从以下代码开始,它展示了一个Picker:
import SwiftUI
struct ContentView: View {
enum MenuOption: String, CaseIterable {
case sort = "Sort"
case search = "Search"
case somethingElse = "Something Else"
}
@State var selectedOption: MenuOption = .sort
var body: some View {
Picker("", selection: $selectedOption) {
ForEach(MenuOption.allCases, id: \.self) {
Text($0.rawValue)
}
}
.pickerStyle(.menu)
}
}
这运行得很顺利。
现在,我想为case sort添加一个二级子菜单,显示两个选项,于是我尝试了如下代码:
struct ContentView: View {
enum MenuOption: String, CaseIterable {
case sort = "Sort"
case search = "Search"
case somethingElse = "Something Else"
}
enum SortOption: String, CaseIterable {
case date = "Date"
case title = "Title"
}
@State var selectedOption: MenuOption = .sort
@State var selectedSort: SortOption = .date
var body: some View {
Picker("", selection: $selectedOption) {
ForEach(MenuOption.allCases, id: \.self) {
if $0 == .sort {
Picker("Sort", selection: $selectedSort) {
ForEach(SortOption.allCases, id: \.self) {
Text($0.rawValue)
}
}
.pickerStyle(.menu)
}
else {
Text($0.rawValue)
}
}
}
.pickerStyle(.menu)
.controlSize(.small)
}
}
乍一看还可以,但如果选择了“Search”,随后把鼠标悬停到Sort > Date时,我本来希望选择器显示“Sort”,但它却仍然显示为“Search”。
我该如何修复?
解决方案
你可以考虑用Picker替换为Menu,然后为外部菜单使用自定义标签。还有其他可能性:
- 如果枚举符合
Identifiable,那么这将简化ForEach。 - 如果排序选项是可选的,那么这将简化标签逻辑。
以下是应用了所有改动的更新示例:
struct ContentView: View {
enum MenuOption: String, CaseIterable, Identifiable {
case sort = "Sort"
case search = "Search"
case somethingElse = "Something Else"
var id: MenuOption { self }
}
enum SortOption: String, CaseIterable, Identifiable {
case date = "Date"
case title = "Title"
var id: SortOption { self }
}
@State var selectedOption: MenuOption = .sort
@State var selectedSort: SortOption? = .date
var body: some View {
Menu {
ForEach(MenuOption.allCases) { option in
if option == .sort {
Menu("Sort") {
ForEach(SortOption.allCases) { sort in
Button(sort.rawValue) {
selectedOption = .sort
selectedSort = sort
}
}
}
}
else {
Button(option.rawValue) {
selectedOption = option
selectedSort = nil
}
}
}
} label: {
Text(selectedSort?.rawValue ?? selectedOption.rawValue)
}
.controlSize(.small)
}
}

编辑 关于你的评论:
唯一的问题是菜单的宽度现在会随着字符串长度而变化。
确实如此。修复的一种方法是应用.buttonSizing(.flexible)。这会让菜单按钮使用所有可用的宽度。然后它可以被限制到实际需要的宽度。
限制宽度的一种方法是使用.frame修饰符和一个固定的最大宽度:
Menu {
// ...
}
.controlSize(.small)
.buttonSizing(.flexible)
.frame(maxWidth: 130)
另外,你也可以根据最宽选项标签的宽度来确定菜单标签的占位宽度。
- 可以使用
ZStack把所有选项标签合并在一起。最宽标签的宽度将决定ZStack的宽度。 - 还需要一些水平填充,以容纳菜单指示符以及原生按钮样式自带的填充。
- 如果使用默认字体(正文),那么当应用
.controlSize(.small)时,这个宽度可能大于菜单按钮所用的字体大小。因此基准宽度可能不需要太多水平填充。
private var footprint: some View {
ZStack(alignment: .leading) {
ForEach(MenuOption.allCases) { option in
Text(option.rawValue)
}
ForEach(SortOption.allCases) { sort in
Text(sort.rawValue)
}
}
.padding(.horizontal)
}
然后把Menu作为覆盖层显示在(隐藏的)基准宽度之上:
var body: some View {
footprint
.hidden()
.overlay {
Menu {
// ...
}
.controlSize(.small)
.buttonSizing(.flexible)
}
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。