Несколько кнопок SwiftUI с всплывающими окнами в поведении HStack
Когда в HStack есть несколько кнопок с всплывающими окнами, я получаю странное поведение. Каждый раз, когда вы нажимаете одну кнопку, всплывающее окно отображается правильно. Но когда вы нажимаете на второй элемент, первое всплывающее окно быстро закрывается, а затем открывается снова. Ожидаемое поведение - закрытие первого всплывающего окна и открытие второго. Xcode 12.5.1, iOS 14.5
Вот мой код:
struct ContentView: View {
var items = ["item1", "item2", "item3"]
var body: some View {
HStack {
MyGreatItemView(item: items[0])
MyGreatItemView(item: items[1])
MyGreatItemView(item: items[2])
}
.padding(300)
}
struct MyGreatItemView: View {
@State var isPresented = false
var item: String
var body: some View {
Button(action: { isPresented.toggle() }) {
Text(item)
}
.popover(isPresented: $isPresented) {
PopoverView(item: item)
}
}
}
struct PopoverView: View {
@State var item: String
var body: some View {
print("new PopoverView")
return Text("View for \(item)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
Спасибо за любую помощь!
1 ответ
Обычно вы бы использовали , но вы получите ошибку ... даже popover(item:content:)
пример в документации дает сбой.
*** Завершение работы приложения из-за неперехваченного исключения «NSGenericException», причина: «UIPopoverPresentationController (<UIPopoverPresentationController: 0x14a109890>) должен иметь не равный нулю sourceView или barButtonItem, установленный до того, как произойдет презентация».
Вместо этого я придумал использовать единственное число
@State presentingItem: Item?
в
ContentView
. Это гарантирует, что все всплывающие окна привязаны к одному и тому же
State
, поэтому вы полностью контролируете, какие из них представлены, а какие нет.
Но,
.popover(isPresented:content:)
с
isPresented
аргумент ожидает. Если это правда, он присутствует, если нет, он будет отклонен. Чтобы преобразовать в
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) {
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)")
}
}
Результат: