如何从列表中选定的行视图中显示弹出窗口?

问题描述 投票:0回答:1

我有一个

List
,我希望用户能够选择多个项目,右键单击以显示选项菜单,对于其中一个选项,显示一个从选定行之一指出的弹出窗口在列表中。到目前为止,我已经能够进行多重选择并显示带有选项的上下文菜单,但是所显示的弹出窗口是针对“列表”显示的,而不是任何选定的行。我该怎么做呢?

目前的情况如下: Xcode preview of content view

这是我的示例内容视图:

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 showPopover = false
    
    var body: some View {
        NavigationStack {
            List (selection: $multiSelectedContacts) {
                ForEach(items) { item in
                    ContentItemView(item: item)
                }
            }
            .toolbar {
                
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            .contextMenu(forSelectionType: Item.ID.self, menu: { _ in
                Button("Label Items", action: {
                    showPopover = true
                })
            })
            .popover(isPresented: $showPopover, content: {
                Text("Test Label")
                    .frame(width: 100, height: 150)
            })
            Text("Select an item")
        }
    }

    private func addItem() {
      ///...
    }
}

struct ContentItemView: View {
    @Environment(\.managedObjectContext) private var viewContext
    let item: Item
    @State var presentConfirmation = false
    var body: some View {
        
        HStack {
            if let timestamp = item.timestamp, let itemNumber = item.itemNumber {
                Text("\(itemNumber) - \(timestamp, formatter: itemFormatter)")
            }
        }
    }
}


private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .long
    return formatter
}()
swiftui swiftui-list
1个回答
0
投票

popover
修饰符需要修改
ContentItemView
,而不是列表。

那么我们将什么传递给

isPresented
呢?我们不能只传递
$showPopover
,因为这会导致弹出窗口显示在所有选定的项目上。因此,我们需要一个自定义
Binding
,仅适用于
multiSelectedContacts
中的一项。假设
Item.ID
Comparable
,我们可以使用
min(by: <)
max(by: <)
找到这样的项目。

let binding = Binding {
    showPopover && multiSelectedContacts.min(by: <) == item.id
} set: {
    // when the popover is dismissed, we should reset showPopover
    if !$0 { showPopover = false }
}
ContentItemView(item: item)
    .popover(isPresented: binding) {
        Text("Popover")
    }

以这种方式精确选择一个项目可能会导致选择当前不可见的项目。例如,可以选择列表中的第一项并滚动到最底部,导致第一项不可见。

要处理这种情况,您还需要跟踪哪些项目是可见的:

@State private var visibleItems = Set<Item.ID>()
let binding = Binding {
    showPopover && visibleItems.intersection(multiSelectedContacts).min(by: <) == item.id
} set: {
    if !$0 { showPopover = false }
}

ContentItemView(item: item)
    .onAppear { visibleItems.insert(item.id) }
    .onDisappear { visibleItems.remove(item.id) }
    .popover(isPresented: binding) {
        Text("Popover")
    }

这是一个完整的最小可重现示例:

struct Item: Identifiable, Hashable {
    let id = UUID()
}

struct ContentView: View {
    private let items: [Item] = (0..<100).map { _ in Item() }
    @State private var multiSelectedContacts = Set<Item.ID>()
    @State private var visibleItems = Set<Item.ID>()
    @State private var showPopover = false
    
    var body: some View {
        NavigationStack {
            List (selection: $multiSelectedContacts) {
                ForEach(items) { item in
                    let binding = Binding {
                        showPopover && visibleItems.intersection(multiSelectedContacts).min(by: <) == item.id
                    } set: {
                        if !$0 {
                            showPopover = false
                        }
                    }
                    
                    ContentItemView(item: item)
                        .onAppear {
                            visibleItems.insert(item.id)
                        }
                        .onDisappear {
                            visibleItems.remove(item.id)
                        }
                        .popover(isPresented: binding) {
                            Text("Popover")
                        }
                }
            }
            .contextMenu(forSelectionType: Item.ID.self, menu: { _ in
                Button("Label Items", action: {
                    showPopover = true
                })
            })
            .onChange(of: multiSelectedContacts) {
                showPopover = false
            }
        }
    }
}

struct ContentItemView: View {
    let item: Item
    
    var body: some View {
        Text(item.id.uuidString)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.