You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 22:08:10