通常你会使用popover(item:content:)
,但你会得到一个错误......即使是文档中的示例也会崩溃。
*** 由于未捕获的异常“NSGenericException”而终止应用程序,原因:“UIPopoverPresentationController (<UIPopoverPresentationController: 0x14a109890>) 应该在演示发生之前设置非零 sourceView 或 barButtonItem。”
我想出的是使用单数@State presentingItem: Item?
in ContentView
。这可确保所有弹出框都绑定到相同的State
,因此您可以完全控制显示哪些弹出框以及不显示哪些弹出框。
但是,.popover(isPresented:content:)
的isPresented
论点需要一个Bool
. 如果这是真的,它会呈现,如果不是,它将被解雇。要转换presentingItem
为Bool
,只需使用自定义Binding
.
Binding(
get: { presentingItem == item }, /// present popover when `presentingItem` is equal to this view's `item`
set: { _ in presentingItem = nil } /// remove the current `presentingItem` which will dismiss the popover
)
然后,presentingItem
在每个按钮的操作中设置。这是事情变得有点棘手的部分 - 我添加了0.5
第二个延迟以确保首先关闭当前显示的弹出框。否则,它不会出现。
if presentingItem == nil { /// no popover currently presented
presentingItem = item /// dismiss that immediately, then present this popover
} else { /// another popover is currently presented...
presentingItem = nil /// dismiss it first
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
presentingItem = item /// present this popover after a delay
}
}
完整代码:
/// make equatable, for the `popover` presentation logic
struct Item: Equatable {
let id = UUID()
var name: String
}
struct ContentView: View {
@State var presentingItem: Item? /// the current presenting popover
let items = [
Item(name: "item1"),
Item(name: "item2"),
Item(name: "item3")
]
var body: some View {
HStack {
MyGreatItemView(presentingItem: $presentingItem, item: items[0])
MyGreatItemView(presentingItem: $presentingItem, item: items[1])
MyGreatItemView(presentingItem: $presentingItem, item: items[2])
}
.padding(300)
}
}
struct MyGreatItemView: View {
@Binding var presentingItem: Item?
let item: Item /// this view's item
var body: some View {
Button(action: {
if presentingItem == nil { /// no popover currently presented
presentingItem = item /// dismiss that immediately, then present this popover
} else { /// another popover is currently presented...
presentingItem = nil /// dismiss it first
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if presentingItem == nil { /// extra check to ensure no popover currently presented
presentingItem = item /// present this popover after a delay
}
}
}
}) {
Text(item.name)
}
/// `get`: present popover when `presentingItem` is equal to this view's `item`
/// `set`: remove the current `presentingItem` which will dismiss the popover
.popover(isPresented: Binding(get: { presentingItem == item }, set: { _ in presentingItem = nil }) ) {
PopoverView(item: item)
}
}
}
struct PopoverView: View {
let item: Item /// no need for @State here
var body: some View {
print("new PopoverView")
return Text("View for \(item.name)")
}
}
结果: