TLDR: В следующем коде нажатие кнопки на верхнем уровне ContentView открывает несколько листов, а затем при закрытии листа он запускается снова; однако щелчок по нему в RowView открывает только один лист. Как я могу открыть только один лист из ContentView?
Вот простая версия того, что у меня есть: стек навигации со списком элементов, каждый из которых имеет кнопку, открывающую лист. Когда лист открывается, он запускает задачу вызова веб-службы и получения некоторых данных для отображения.
Проблема в том, что, согласно моей отладке, открывается несколько листов, каждый из которых запускает задачу, а некоторые завершаются сбоем из-за дублирующего вызова. Я хотел бы решить эту проблему, чтобы лист появлялся только один раз. Как я могу заставить это работать?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink {
Text("Hello")
} label: {
RowView(textToShow: "Text 1")
}
NavigationLink {
Text("Hello")
} label: {
RowView(textToShow: "Text 2")
}
}
}
}
}
struct RowView: View {
var textToShow: String
@State private var showingPopover = false
var body: some View {
Text("Hello")
Button(action: {
//showingPopover = true
print("Button pressed")
}, label: {
Image(systemName:"doc.text")
.imageScale(.large)
})
.onTapGesture {
print("Button tapped")
showingPopover = true
}
.sheet(isPresented: $showingPopover, content: {
PopupView(textToShow: textToShow)
.onAppear {
print("Sheet appeared")
}
})
}
}
struct PopupView: View {
var textToShow: String
@State private var loading = true
var body: some View {
VStack {
if loading {
HStack{
ProgressView()
Text("Loading...")
}
} else {
Text(textToShow)
}
}
.onAppear() {
print("Loading")
loading = false
}
}
}
#Preview {
ContentView()
}
#Preview {
RowView(textToShow: "Text 1")
}
Нажатие кнопки, а затем закрытие листа приводит к следующему результату в консоли:
Нажата кнопка
Загрузка
Лист появился
Загрузка
Лист появился
Загрузка
Лист появился
Я попытался использовать переменную состояния, чтобы фиксировать, когда всплывающее окно уже открыто, а затем отображать содержимое листа только в том случае, если всплывающее окно не было открыто; Я попытался установить переменную привязки на true
только для первого жеста касания, но это тоже не решение.
Основываясь на другом аналогичном вопросе, я думаю, что мне нужно переместить лист вверх, что немного сложнее, чем захватить текст для конкретной строки (я использую Swift уже около 2 недель? Так что не совсем к этому привык), но Я думаю, это может сработать
Я ожидаю, что только один лист загрузит и выполнит задачу.
Спасибо! Я использую Xcode 15.3 и iPhone 15. Кстати, я сделал несколько небольших обновлений кода, чтобы лучше отразить мой фактический вариант использования (каждая строка будет иметь переменную для текста, отображаемого во всплывающем окне).
ОК, я попробую на другой машине — Обновлено: да, я получаю то же самое на Xcode 15.2, хотя журналы появляются в 2 раза вместо 3х.
Хорошо, спасибо за подтверждение того, что это новинка для этой версии Xcode. Для меня 3x было, когда я закрывал лист (кажется, странное время для вызова листа onAppear)
о, тогда да, с моей стороны то же самое. Похоже, это ошибка, хотя в целом onAppear
сложно использовать для определенных типов логики.
Не уверен, что это применимо, но вместо этого вы можете использовать .task. Обновлено: о, но, похоже, это также вызывается несколько раз :S Обратите внимание, что эта проблема, похоже, специфична для представлений внутри NavigationLink
— удалите это, и этого не произойдет (хотя, очевидно, если вам нужна навигация, это не так просто).
Да, и я должен упомянуть, что, хотя я, как и вы, вижу дубликаты журналов, я не видел, чтобы они представляли несколько листов одновременно.
Да, я думаю, что в этом примере несколько листов не очевидны, но я думаю, что это происходит потому, что когда я запускаю свой реальный код, где каждый лист запускает вызов веб-службы, один лист показывает результаты вызова (согласно отладке), а второй терпит неудачу. (пытается одновременно вызвать) и отображает текст «Загрузка» по умолчанию. Я пытаюсь переместить лист вверх и думаю, что это сработает, но мне немного сложно работать с привязками.
Хорошо, я думаю, что нашел решение: переместив лист в NavigationStack, он появится только один раз. Чтобы комментарий исходил из переменной, зависящей от строки, я просто использую переменную привязки. В этом случае я использовал isPresented
и отдельную переменную, но в моем случае я думаю, что буду использовать вход item
для листа.
import SwiftUI
struct ContentView: View {
@State var showingPopover: Bool = false
@State var textToShow: String = ""
var body: some View {
NavigationStack {
List {
NavigationLink {
Text("Hello")
} label: {
RowView(showingPopover: $showingPopover, textToShow: $textToShow, textValue: "Text 1")
}
NavigationLink {
Text("Hello")
} label: {
RowView(showingPopover: $showingPopover, textToShow: $textToShow, textValue: "Text 2")
}
}
}
.sheet(isPresented: $showingPopover, onDismiss: {
print("Sheet dismissed")
showingPopover = false
textToShow = ""
}, content: {
PopupView(textToShow: textToShow)
.onAppear {
print("Popup appeared")
}
})
}
}
struct RowView: View {
@Binding var showingPopover: Bool
@Binding var textToShow: String
var textValue: String
var body: some View {
Text("Hello")
Button(action: {
//showingPopover = true
print("Button pressed")
}, label: {
Image(systemName:"doc.text")
.imageScale(.large)
})
.onTapGesture {
print("Button tapped")
textToShow = textValue
showingPopover = true
}
}
}
struct PopupView: View {
var textToShow: String
@State private var loading = true
var body: some View {
VStack {
if loading {
HStack{
ProgressView()
Text("Loading...")
}
} else {
Text(textToShow)
}
}
.onAppear() {
print("Loading")
loading = false
}
}
}
#Preview {
ContentView()
}
Какую версию Xcode и iOS вы используете? Попробовал ваш код в Xcode 14.3.1, но не вижу проблемы. Я могу попробовать Xcode 15, если это применимо к вам. Кстати, вам следует обернуть
RowView
в представление, напримерHStack
илиVStack
, но похоже, что это вообще не связано с вашей проблемой.