SwiftUI: Представление может быть закэшировано, если у него есть связь с предыдущим представлением, которое переходит к этому представлению.

Вопрос или проблема

У меня есть два представления: 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 оптимизирует представления и кэширует их состояние для повышения производительности.

Решение проблемы

Существует несколько подходов к решению вашей проблемы, которые помогут избежать кэширования состояния представления:

  1. Используйте @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, и при каждом открытии у него будет чистое состояние.

  2. Перезапуск NavigationStack:

    Если требуется, чтобы AnnotationView создавался заново при каждом открытии, вы можете рассмотреть возможность перезапуска NavigationStack. Это можно сделать при помощи смены параметров, которые уникальны для каждого доступа:

    .navigationDestination(isPresented: $navigateToAnnotationView) {
       AnnotationView(selection: sharedImageData.segmentedIndices, id: UUID())
    }

    В данном случае id: UUID() будет создавать уникальный идентификатор каждый раз, когда вы переходите к AnnotationView, тем самым заставляя SwiftUI рассматривать это как новое представление.

  3. Логика закрытия представления:

    Убедитесь, что ваше условие закрытия представления корректно обрабатывается. Вы можете переместить вашу логику в .onAppear, чтобы гарантировать, что если isValid возвращает false, элемент закрывается при каждом открытии представления.

Заключение

Использование @State или уникального идентификатора при передаче представлений поможет избежать проблем с кэшированием состояния и допустит перезапуск AnnotationView. Таким образом, вы сможете легко управлять состоянием и пересоздавать представление при необходимости.

Оцените материал
Добавить комментарий

Капча загружается...