SwiftUI:如何从List的选中行视图显示Popover?
解决SwiftUI多选List中Popover指向选中行的问题
要让Popover锚定到选中的列表行,核心是把Popover的展示逻辑绑定到具体的列表条目上,而非整个List容器。以下是具体实现步骤:
1. 调整状态变量
将原来的showPopover布尔值替换为可选的Item.ID,用来记录需要展示Popover的目标条目ID:
@State private var popoverTargetID: Item.ID? // 替换原showPopover变量
2. 修改上下文菜单触发逻辑
在上下文菜单的按钮点击时,从选中的集合中选取一个目标条目(这里取第一个选中的条目作为锚点):
.contextMenu(forSelectionType: Item.ID.self, menu: { selectedIDs in Button("Label Items", action: { popoverTargetID = selectedIDs.first // 从选中集合中取第一个ID作为锚点 }) })
3. 将Popover绑定到列表条目
把Popover从List层级移到ContentItemView内部,利用popover(item:)修饰符,仅当条目的ID匹配popoverTargetID时才展示Popover:
修改ContentItemView:
struct ContentItemView: View { @Environment(\.managedObjectContext) private var viewContext let item: Item @Binding var popoverTargetID: Item.ID? // 添加绑定变量 var body: some View { HStack { if let timestamp = item.timestamp, let itemNumber = item.itemNumber { Text("\(itemNumber) - \(timestamp, formatter: itemFormatter)") } } .popover(item: $popoverTargetID) { targetID in // 仅当当前条目ID等于目标ID时展示Popover if targetID == item.id { Text("Test Label") .frame(width: 100, height: 150) } } } }
在List中传递绑定:
ForEach(items) { item in ContentItemView(item: item, popoverTargetID: $popoverTargetID) }
完整修改后的代码
struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], animation: .default) private var items: FetchedResults<Item> @State private var multiSelectedContacts = Set<Item.ID>() @State private var popoverTargetID: Item.ID? var body: some View { NavigationStack { List (selection: $multiSelectedContacts) { ForEach(items) { item in ContentItemView(item: item, popoverTargetID: $popoverTargetID) } } .toolbar { ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } .contextMenu(forSelectionType: Item.ID.self, menu: { selectedIDs in Button("Label Items", action: { popoverTargetID = selectedIDs.first }) }) Text("Select an item") } } private func addItem() { ///... } } struct ContentItemView: View { @Environment(\.managedObjectContext) private var viewContext let item: Item @Binding var popoverTargetID: Item.ID? var body: some View { HStack { if let timestamp = item.timestamp, let itemNumber = item.itemNumber { Text("\(itemNumber) - \(timestamp, formatter: itemFormatter)") } } .popover(item: $popoverTargetID) { targetID in if targetID == item.id { Text("Test Label") .frame(width: 100, height: 150) } } } } private let itemFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .long return formatter }()
逻辑说明
- 使用
popover(item:)修饰符替代isPresented:,可以精准绑定到具体的数据条目,让Popover自动锚定到对应的列表行。 - 从选中集合中取第一个条目作为锚点,你也可以根据需求调整逻辑(比如记录用户右键点击的具体条目,不过多选上下文菜单默认针对选中集合,取第一个是合理方案)。
内容的提问来源于stack exchange,提问作者Z S
相关产品推荐
相关产品推荐

