Вопрос или проблема
У меня есть два представления: ContentView и AnnotationView. Представление ContentView имеет навигационное направление, которое ведет к AnnotationView, которое отображается, когда определенное свойство истинно.
Вот исходный код ContentView (можно, вероятно, проигнорировать CameraManager для этой проблемы)
struct ContentView: View {
var selection: [Int]
@State private var manager: CameraManager?
@State private var navigateToAnnotationView = false
var body: some View {
VStack {
if manager?.dataAvailable ?? false {
ZStack {
// некоторое представление
}
Button {
manager!.startPhotoCapture()
// Произвольная задержка
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
navigateToAnnotationView = true
}
} label: {
Image(systemName: "camera.circle.fill")
.resizable()
.frame(width: 60, height: 60)
.foregroundColor(.white)
}
} else {
VStack {
SpinnerView()
Text("Настройки камеры в процессе")
.padding(.top, 20)
}
}
}
.navigationDestination(isPresented: $navigateToAnnotationView) {
AnnotationView(selection: sharedImageData.segmentedIndices, navigateToAnnotationView: $navigateToAnnotationView)
}
.navigationBarTitle("Камера", displayMode: .inline)
.onAppear {
if (manager == nil) {
manager = CameraManager(sharedImageData: sharedImageData)
} else {
manager?.resumeStream()
}
}
.onDisappear {
manager?.stopStream()
}
.onChange(of: navigateToAnnotationView) { newNavigateToAnnotationView in
if (!newNavigateToAnnotationView && (manager != nil)) {
manager?.resumeStream()
}
}
}
}
AnnotationView, как упоминалось, имеет привязку к свойству navigateToAnnotationView. Вот код для AnnotationView:
struct AnnotationView: View {
@State private var index = 0
let options = ["Я согласен с этой аннотацией", "Я не согласен с этой аннотацией"]
@State private var selectedOptionIndex: Int? = nil
var selection: [Int]
@Environment(\.dismiss) var dismiss
@Binding var navigateToAnnotationView: Bool
var body: some View {
if (!self.isValid()) {
Rectangle()
.frame(width: 0, height: 0)
.onAppear() {
print("AnnotationView isValid 1 \(self.selection.isEmpty), \(index) vs \(self.selection.count)")
navigateToAnnotationView = false
self.dismiss()
}
} else {
ZStack {
VStack {
ForEach(0.. Bool {
if (self.selection.isEmpty || (index >= self.selection.count)) {
return false
}
return true
}
func nextSegment() {
self.index += 1
}
func refreshView() {
// Любая дополнительная логика обновления может быть размещена здесь
// Например: получение новых данных, вызов анимаций, отправка текущих данных и т.д.
}
}
Попытка в AnnotationView заключается в том, что если isValid() возвращает false, то я немедленно возвращаюсь к ContentView. Хотя это, кажется, работает, проблема, с которой я сталкиваюсь, заключается в том, что когда я снова нажимаю кнопку на ContentView, ‘новый’ AnnotationView, который открывается, все еще сохраняет те же переменные состояния, что и предыдущий экземпляр AnnotationView.
Это, похоже, не происходит, когда я убираю привязку navigateToAnnotationView. Однако, когда я это делаю, обратная навигация к ContentView не работает должным образом, так как ни onAppear, ни onChange не срабатывают.
Могу ли я узнать, что может происходить с привязкой? Кэшируется ли AnnotationView для повышения производительности?
Ответ или решение
Ваша проблема заключается в том, что AnnotationView
кэшируется, когда вы используете Binding
с @State
переменной navigateToAnnotationView
. Когда вы вновь открываете AnnotationView
, он не инициализируется заново, а использует существующее состояние. Это поведение происходит, поскольку SwiftUI оптимизирует представления и кэширует их состояние для повышения производительности.
Решение проблемы
Существует несколько подходов к решению вашей проблемы, которые помогут избежать кэширования состояния представления:
-
Используйте
@State
вместо@Binding
для управления состоянием:Вместо того чтобы передавать
Binding
отContentView
вAnnotationView
, вы можете использовать локальный@State
вAnnotationView
:struct AnnotationView: View { @State private var index = 0 @State private var isPresented = true // остальные переменные инициализации var body: some View { // Проверяем, нужно ли закрыть представление if !isPresented { // Закрываем представление dismiss() } else { // Ваш текущий код отображения AnnotationView } } // Обновляйте isPresented, когда нужно закрыть представление // Например, вызвать isPresented = false в .onAppear или .onChange }
При таком подходе теперь
AnnotationView
не будет зависеть от состоянияContentView
, и при каждом открытии у него будет чистое состояние. -
Перезапуск NavigationStack:
Если требуется, чтобы
AnnotationView
создавался заново при каждом открытии, вы можете рассмотреть возможность перезапускаNavigationStack
. Это можно сделать при помощи смены параметров, которые уникальны для каждого доступа:.navigationDestination(isPresented: $navigateToAnnotationView) { AnnotationView(selection: sharedImageData.segmentedIndices, id: UUID()) }
В данном случае
id: UUID()
будет создавать уникальный идентификатор каждый раз, когда вы переходите кAnnotationView
, тем самым заставляя SwiftUI рассматривать это как новое представление. -
Логика закрытия представления:
Убедитесь, что ваше условие закрытия представления корректно обрабатывается. Вы можете переместить вашу логику в
.onAppear
, чтобы гарантировать, что еслиisValid
возвращаетfalse
, элемент закрывается при каждом открытии представления.
Заключение
Использование @State
или уникального идентификатора при передаче представлений поможет избежать проблем с кэшированием состояния и допустит перезапуск AnnotationView
. Таким образом, вы сможете легко управлять состоянием и пересоздавать представление при необходимости.