Ошибка выбора средства выбора SwiftUI и поле поиска, приводящие к исчезновению элементов списка

В этом коде при выборе элементов в средстве выбора возникают две проблемы:

Эта ошибка возникает при выборе элемента в первом средстве выбора. Выбор кажется недействительным, и я получаю указанную выше ошибку как для родительских, так и для дочерних элементов.

Проблема с полем поиска: когда я использую поле поиска, все элементы в списке исчезают, хотя совпадения должны быть. Может ли кто-нибудь помочь мне решить эти проблемы? Вот соответствующий фрагмент кода:

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()
            }
        }
    }
}

Что может быть причиной этих проблем и как их исправить? Или есть лучший способ добиться двух сборщиков по отношению к родительскому/дочернему элементу, категории/подкатегории и т.д.

Я настоятельно рекомендую вам использовать struct Parent: Identifiable { let id = UUID() ...} и struct Child: Identifiable { let id = UUID() ...}, потому что Pickers, ForEach и т. д. ожидайте, что элементы будут уникальными. Не используйте ForEach(vm.searchParents, id: \.self), просто ForEach(vm.searchParents).

workingdog support Ukraine 07.09.2024 00:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, у вас несоответствие типов. Первый сборщик привязан к $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()
// }

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

miltenkot 07.09.2024 06:54

Другие вопросы по теме