Подробный вид/назначение NavigationSplitView
не меняется при выборе другого элемента на боковой панели.
Учитывая модель данных Swift
@Model final class MyModel {
var text: String
init(text: String) {self.text = text}
}
И NavigationSplitView, который должен отображать все эти модели плюс представление редактирования.
struct MyModelEditView: View {
@Environment(\.modelContext) var modelContext
@State var myModel: MyModel
var body: some View {
TextField("Edit Text", text: $myModel.text)
}
}
struct ContentView: View {
@Environment(\.modelContext) var modelContext
@Query var models: [MyModel]
var body: some View {
NavigationSplitView {
List {
ForEach(myModels) { model in
NavigationLink {
MyModelEditView(myModel: model)
.modelContext(modelContext)
} label: {
Text(model.text)
}
}
}
} detail: {
Text("Tap plus for a new model")
}
// we pretend that here is a button in a toolbar that adds a new model
}
При первом нажатии на любой элемент в списке отображается подробный вид этого элемента! Но при нажатии на другую модель подробное представление просто остается там, где оно было с предыдущей моделью.
Я также пытался создать
@State var selectedModel: MyModel
и используя инициализатор списка List(selection: $selectedModel)
, помечая каждый элемент в ForEach
(.tag(model)
) вместо использования NavigationLink
, а затем в блоке подробностей:
detail: {
if let selectedModel {
MyModelEditView(myModel: myModel)
.modelContext(modelContext)
} else {
Text("Tap plus for a new model")
}
}
... Но и это не помогло.
Я обнаружил, что .navigationDestination(for:)
ограничен NavigationStack
, поэтому понятия не имею, как создать работающий стек навигации для моих моделей SwiftData.
! Обратите внимание, что это не характерно для моделей SwiftData как объектов, это также происходит с любыми структурами или классами. Я пробовал это со структурой, содержащей только одну строку, например с классом MyModel
.
Я нашел решение после долгих проб и ошибок. Решением было просто сделать это в списке:
ForEach(myModels) { model in
NavigationLink {
MyModelEditView(myModel: model)
.modelContext(modelContext)
.id(model.id) // <-- added .id(model.id)
} label: {
Text(model.text)
}
}
Судя по тому, что я прочитал, .id заставляет каждый раз заново рисовать подробное представление.
Я не уверен, что это самый элегантный способ сделать это, но он работает.
Это не исправление, а обходной путь. State создает глубокую копию Observable. Привязываемый нет. Кроме того, государство всегда должно быть частным.
Как было предложено в комментариях, @Bindable
устранит проблему без использования другого модификатора представления.
struct MyModelEditView: View {
@Bindable var myModel: MyModel
var body: some View {
TextField("Edit Text", text: $myModel.text)
}
}
Я попробовал, и это действительно решает проблему, спасибо. Хотя мне кажется немного странным, что оболочка свойства в целевом представлении меняет поведение NavigationSplitView.
Речь идет о том, как SwiftUI получает уведомление об обновлении. Состояние является внутренним для представления, в котором оно объявлено, но Bindable уведомит родительское представление.
Измените переменную состояния на let или Bindable.