В этом коде при выборе элементов в средстве выбора возникают две проблемы:
Эта ошибка возникает при выборе элемента в первом средстве выбора. Выбор кажется недействительным, и я получаю указанную выше ошибку как для родительских, так и для дочерних элементов.
Проблема с полем поиска: когда я использую поле поиска, все элементы в списке исчезают, хотя совпадения должны быть. Может ли кто-нибудь помочь мне решить эти проблемы? Вот соответствующий фрагмент кода:
import SwiftUI
struct Parent: Hashable {
let title: String
let children: [Child]
}
struct Child: Hashable {
let title: String
}
extension Parent {
static var mock: [Parent] = [
.init(title: "Parent 1", children: [.init(title: "Child 1"), .init(title: "Child 2")]),
.init(title: "Parent 2", children: [.init(title: "Child 3"), .init(title: "Child 4"), .init(title: "Child 5")])
]
}
@MainActor
@Observable
final class TestViewModel {
var parents: [Parent] = Parent.mock
var selectedParent: Parent
var searchParentText = ""
var searchParents: [Parent] {
if searchParentText.isEmpty {
return parents
} else {
return parents.filter { $0.title.contains(searchParentText) }
}
}
var children: [Child] {
selectedParent.children
}
var selectedChild: Child
var searchChildText = ""
var searchChildren: [Child] {
if searchChildText.isEmpty {
return children
} else {
return children.filter { $0.title.contains(searchChildText) }
}
}
init() {
self._selectedParent = .mock[0]
self._selectedChild = Parent.mock[0].children[0]
}
func updateSelectedItems() {
self.selectedChild = self.selectedParent.children[0]
}
}
@MainActor
struct TestSwiftUIView: View {
@State var vm = TestViewModel()
var body: some View {
NavigationStack {
Form {
Picker("Parents",
selection: $vm.selectedParent) {
ForEach(vm.searchParents, id: \.self) { parent in
Text(parent.title)
.tag(parent.title)
.searchable(text: $vm.searchParentText)
}
}
.pickerStyle(.navigationLink)
Picker("Parents",
selection: $vm.selectedChild) {
ForEach(vm.searchChildren, id: \.self) { child in
Text(child.title)
.tag(child.title)
.searchable(text: $vm.searchChildText)
}
}
.pickerStyle(.navigationLink)
}
.onChange(of: vm.selectedParent) {
vm.updateSelectedItems()
}
}
}
}
Что может быть причиной этих проблем и как их исправить? Или есть лучший способ добиться двух сборщиков по отношению к родительскому/дочернему элементу, категории/подкатегории и т.д.
Во-первых, у вас несоответствие типов. Первый сборщик привязан к $vm.selectedParent
, который имеет тип Parent
. Теги имеют значение parent.title
, которое относится к типу String
. Теги должны иметь тот же тип, что и связанная переменная. То же самое относится и ко второму сборщику.
В действительности, тот способ, который у вас есть сейчас, действительно может работать, по крайней мере, для дочернего элемента, поскольку значения тегов сравниваются с выбираемыми элементами по их хеш-значению. Тем не менее, я бы посоветовал вам изменить теги на следующие:
Text(parent.title)
.tag(parent)
//...
Text(child.title)
.tag(child)
Это изменение не решит проблему, о которой вы сообщили. Причина, по которой это происходит, заключается в том, что второй сборщик содержит дочерние элементы выбранного родителя. Когда вы меняете выбор с Родителя 1 на Родителя 2, второй элемент выбора автоматически заполняется дочерними элементами Родителя 2. Однако ранее выбранный дочерний элемент принадлежал Родителю 1. Этот дочерний элемент больше не находится среди дочерних элементов Родителя. 2, поэтому вы получаете ошибку.
Когда выбор родителя изменяется, вы вызываете updateSelectedItems
, чтобы выбрать первого дочернего элемента вновь выбранного родителя. К сожалению, это происходит слишком поздно. Средство выбора дочерних элементов динамически заполняется заново при изменении родительского выбора, и когда это происходит, оно не может сохранить предыдущий дочерний выбор.
Один из способов исправить это — обновить модель представления:
selectedParent
и обновите новую переменную в willSet
и didSet
:// TestViewModel
private var previouslySelectedParent: Parent?
var selectedParent: Parent {
willSet {
if newValue != selectedParent {
previouslySelectedParent = selectedParent
}
}
didSet {
selectedChild = selectedParent.children[0]
previouslySelectedParent = nil
}
}
Затем обновите вычисляемое свойство, которое доставляет дочерние элементы:
var children: [Child] {
var combinedChildren = selectedParent.children
if let previouslySelectedParent, previouslySelectedParent != selectedParent {
combinedChildren += previouslySelectedParent.children
}
return combinedChildren
}
Наконец, функция didSet
на selectedParent
теперь выполняет работу updateSelectedItems
, поэтому вы можете удалить эту функцию и удалить обратный вызов .onChange
:
// func updateSelectedItems() {
// self.selectedChild = self.selectedParent.children[0]
// }
// TestSwiftUIView
Form {
// ...
}
// .onChange(of: vm.selectedParent) {
// vm.updateSelectedItems()
// }
Эй, это работает, спасибо. К сожалению, это не решает проблему с возможностью поиска, которая до сих пор не работает, но я думаю, что могу удалить ее на данный момент, чтобы удовлетворить свои потребности.
Я настоятельно рекомендую вам использовать
struct Parent: Identifiable { let id = UUID() ...}
иstruct Child: Identifiable { let id = UUID() ...}
, потому чтоPickers
,ForEach
и т. д. ожидайте, что элементы будут уникальными. Не используйтеForEach(vm.searchParents, id: \.self)
, простоForEach(vm.searchParents)
.