Передача ViewModel в View

У меня есть модель представления:

@MainActor public class TestViewModel: ObservableObject {
    @Published var data: [String: [String]]
    
    init() {
        self.data = ["a": ["a1", "a2"]]
    }
}

Существует вложенное представление, которое использует эти данные:

struct ContainerView: View {
    @Binding var data: [String: [String]]
    
    var body: some View {
        List() {
            ForEach(data.keys.sorted(), id: \.self){ key in
                Text(key)
            }
        }
    }
}

struct TestView: View {
    @StateObject private var viewModel = TestViewModel()
    
    var body: some View {
        ContainerView(
            data: $viewModel.data
        )
    }
}

Это работает так, как ожидалось.

Но предположим, что у меня есть много разных типов опубликованных данных. Было бы неплохо, если бы я мог просто передать саму модель представления, что я действительно могу сделать:

struct ContainerView: View {
    @ObservedObject var viewModel: TestViewModel
    
    var body: some View {
        List() {
            ForEach($viewModel.data.keys.sorted(), id: \.self){ key in
                Text(key)
            }
        }
    }
}

struct TestView: View {
    @StateObject private var viewModel = TestViewModel()
    
    var body: some View {
        ContainerView(
            viewModel: viewModel
        )
    }
}

Это отлично работает для большинства случаев использования ViewModel внутри ContainerView (которые я опустил для краткости), но не для этого. В ForEach я получаю следующие ошибки:

Невозможно назначить свойство: «keys» — это свойство, доступное только для получения.

Невозможно вызвать значение нефункционального типа «Binding<() -> [Dictionary<String, [String]>.Keys.Element]>» (также известное как «Binding<() -> Array>»).

Поиск элемента динамического ключевого пути не может ссылаться на метод экземпляра sorted().

Что здесь происходит?

viewModel.data является изменяемым, но viewModel.data.keys доступен только для чтения. Когда вы передаете $viewModel.data.keys, это Binding, и Binding ожидает записи в путь, в котором находится переменная, в данном случае viewModel.data.keys, который доступен только для чтения.
Aswath 04.07.2024 14:14

Но не является ли это также привязкой в ​​первом случае? Где-нибудь в ForEach есть ли попытка записи в свойство (не .sorted() просто читает)?

cheersmate 04.07.2024 14:19

Думаю, нет. При привязке вы используете этот API: ForEach(Binding<MutableCollection & RandomAccessCollection>, id: KeyPath<C.Element, _>) { Binding<C.Element> -> _}. С первым вы получаете доступ к этому: ForEach(RandomAccessCollection, content: (Identifiable) -> View)

Aswath 04.07.2024 14:24

Почему вы хотите использовать привязку? просто используйте «ForEach(viewModel.data.keys.sorted()». Если вы хотите изменить ky или содержимое данных, это работа ViewModel, и, поскольку вы получаете ее как ObservedObject, проблем нет.

Ptit Xav 04.07.2024 21:17

@PtitXav Если вы опубликуете ответ, я смогу его принять.

cheersmate 10.07.2024 15:03
Стоит ли изучать 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
5
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Просто используйте «ForEach(viewModel.data.keys.sorted() Если вы хотите изменить ky или содержимое данных, это работа ViewModel, и когда вы получаете его как ObservedObject, проблем не возникает.

@MainActor public class TestViewModel: ObservableObject {
    @Published var data: [String: [String]]
    
    init() {
        self.data = ["a": ["a1", "a2"]]
    }

    // to update data
    func updateData(for key: String, strings: [String]) {
        self.data[key] = strings
    }

    // to add data
    func addData(for key: String, strings: [String]) {
        self.data[key] = strings
    }
}

struct ContainerView: View {
    @ObservedObject var viewModel: TestViewModel
    
    var body: some View {
        List() {
            Button("add key b") {
                viewModel.addData(for: "b", strings: ["b1", "b2"])
            }
            Button("update key b") {
                viewModel.addData(for: "b", strings: ["b3", "b4"])
            }
            Button("add key c") {
                viewModel.addData(for: "c", strings: ["c1", "c2"])
            }
            ForEach(viewModel.data.keys.sorted(), id: \.self){ key in
                let strings = viewModel.data[key]!.joined(separator: "-")
                Text(key + " = " + strings)
            }
        }
    }
}

struct TestView: View {
    @StateObject private var viewModel = TestViewModel()
    
    var body: some View {
        ContainerView(
            viewModel: viewModel
        )
    }
}

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