Почему SwiftUI автоматически перемещается назад при обновлении моего базового источника данных и как избежать такого поведения?

У меня довольно обычная установка:

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

Проблема в том, что обновление базового элемента (который является структурой) заставляет SwiftUI автоматически перемещаться назад. Я предполагаю, что это связано с тем, что структура является неизменным значением и уничтожается во время обновления, однако она соответствует Identifiable, поэтому я ожидаю, что SwiftUI поймет, что элемент все еще существует и его нужно просто обновить, а не уничтожить.

Есть ли способ обновить основной список, не выходя из подробного представления?

Вот минимальный воспроизводимый пример.

import SwiftUI

struct ContentView: View {
    var body: some View {
       DemoList(viewModel: ViewModel())
    }
}

struct DemoItem: Codable, Hashable, Identifiable {
    var id: UInt
    var description: String
}

final class ViewModel: ObservableObject, Identifiable {
    @Published var list = [
        DemoItem(id: 1, description: "One"),
        DemoItem(id: 2, description: "two")
    ]
    
    /// This update causes SwiftUI to automatically navigate away from the detail view
    func update(item: DemoItem) {
        list = list.map { $0.id == item.id ? item : $0 }
    }
}

struct DemoList: View {
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        NavigationView {
            ForEach(viewModel.list, id: \.self) { item in
                NavigationLink(destination: DemoDetail(viewModel: self.viewModel, item: item)) {
                    Text(item.description)
                }
            }
        }
    }
}

struct DemoDetail: View {
    @ObservedObject var viewModel: ViewModel
    var item: DemoItem
    
    var body: some View {
        Text(item.description)
            .onTapGesture {
                let newItem = DemoItem(id: self.item.id, description: UUID().uuidString)
                self.viewModel.update(item: newItem)                
            }
    }
}

person Robert    schedule 03.07.2020    source источник


Ответы (1)


Просто удалите идентификатор из ForEach, и он перестанет возвращаться назад

ForEach(viewModel.list) { item in
      NavigationLink(destination: DemoDetail(viewModel: self.viewModel, item: item)) {
      Text(item.description)
    }
}
person Ludyem    schedule 03.07.2020
comment
Хм, работает! Не могли бы вы объяснить, почему это работает? - person Robert; 03.07.2020
comment
Это связано с тем, что вы аннулируете NavigationLink, посмотрите ответ в этом вопросе: stackoverflow.com/questions/58302913/ - person Ludyem; 03.07.2020