Представление списка не обновляется в сценарии MVVM

Я создал небольшой образец для демонстрации проблемы. У меня есть класс ItemContent, который является контейнером для строк типа ContentRow. ContentView показывает простой список редактируемых RowViews, а функция добавления новой строки обрабатывается внутри ViewModel.

По какой-то причине список не обновляется при добавлении строк. Я протестировал несколько комбинаций оберток состояний, но не смог понять, как обновить пользовательский интерфейс из обновленного vm.itemContent.children. Значит, я здесь явно что-то упускаю.

class ItemContent: Identifiable, ObservableObject {
    //container for content rows/ content tree.
    
    @Published var id = UUID()
    @Published var children: [ContentRow] = []
}

class ContentRow: Identifiable, Hashable, ObservableObject {
    @Published var id = UUID()
    @Published var title = "new"
    @Published var children: [ContentRow] = []
    
    static func ==(lhs: ContentRow, rhs: ContentRow) -> Bool {
        lhs.id == rhs.id
    }
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

//MARK: - views/ view model.

struct ContentView: View {
    @StateObject var vm = ViewModel()
    
    var body: some View {
        NavigationStack {
            List(selection: $vm.selectedRows) {
                ForEach(vm.itemContent.children, id: \.id) { row in
                    RowView(row: row)
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("add", action: vm.add)
                }
            }
        }
    }
}
struct RowView: View {
    @ObservedObject var row: ContentRow
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField("editable text", text: $row.title)
            Text(row.id.uuidString)
        }
    }
}

extension ContentView {
    class ViewModel: ObservableObject {
        @Published var itemContent = ItemContent()
        @Published var selectedRows = Set<ContentRow>()
        
        func add() {
            let row = ContentRow()
            itemContent.children.append(row)
        }
    }
}

Я также попробовал решение здесь, но id не помог. 1

Добавьте ObservableObject в ContentView, добавьте Published в свойства и удалите пользовательские Hashable и Equatable. Затем избавьтесь от всего кода строки привязки и используйте ObservedObject.

lorem ipsum 02.09.2024 15:45

Спасибо. Вы действительно имели в виду «добавить ObservableObject в ContentView»? Не уверен, что я уловил суть.

MatFetsch 02.09.2024 17:32

Строка, извините, не посмотреть

lorem ipsum 02.09.2024 17:38

Спасибо. Я не могу удалить Hashable и Equatable из-за использования Set<ContentRow> для выбора списка. Я обновил ContentRow с помощью Observable и Publish, изменив привязку на ObservedObject в RowView. Но он по-прежнему не обновляет список.

MatFetsch 02.09.2024 17:49

Затем включите все свойства, а не только идентификатор. SwiftUI использует его, он буквально говорит SwiftUI рендериться только при изменении идентификатора.

lorem ipsum 02.09.2024 17:51

Содержимое товара также необходимо соблюдать, посмотрите ссылку.

lorem ipsum 02.09.2024 17:55

Обратите внимание: если вы перейдете на Observable, все эти слои не понадобятся.

lorem ipsum 02.09.2024 17:58

Я добавил обновленный код, как я понял ваши комментарии и комментарии в упомянутом посте, но он все еще не обновляет список.

MatFetsch 02.09.2024 18:07

Вы по-прежнему не наблюдаете за содержимым элемента и не настроили реализации пользовательских протоколов.

lorem ipsum 02.09.2024 19:29
Стоит ли изучать 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
10
117
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

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

struct ItemContent {
    @Published var id = UUID()
    @Published var children: [ContentRow] = []
}

struct ContentRow: Identifiable {
    var id = UUID()
    var title = "new"
    var children: [ContentRow] = []
}

Также нет необходимости устанавливать ItemContent как Identifiable, поскольку значения, передаваемые в список, имеют тип ContentRow.

Обновленный ответ

Чтобы решить проблему с добавлением элементов, просто измените модели на типы значений. А что касается темы выбора, просто используйте инициализатор компонента List следующим образом:

List($vm.itemContent.children, id: \.self, selection: $vm.selectedRows) {
    RowView(row: $row)
}

И добавьте EditButton для поддержки режима редактирования:

.toolbar {
    ...
    EditButton()
}

Привет, po24, спасибо. Да, структуры здесь работают. Но как мне реализовать выбор списка с помощью Set<ContentRow> при использовании только структур. Компилятор говорит мне использовать класс, который должен быть хешируемым. Опознаваемый был случайно скопирован из моего проекта туда, где он был нужен.

MatFetsch 03.09.2024 06:58

Что касается проблемы выбора, вы неправильно используете API компонента List. Просто установите это так: List($vm.itemContent.children, id: \.self, selection: $vm.selectedRows) { RowView(row: $row) } И все готово.

po24 03.09.2024 08:53

не следует иметь ItemContent с @Published, это неправильно.

workingdog support Ukraine 04.09.2024 00:17

Попробуйте этот подход, создавая structs вместо классов для ItemContent и ContentRow. У меня работает хорошо, добавление новых строк отображается в списке. Протестировано на реальном устройстве iOS-18, MacCatalyst и MacOS 15.

class ViewModel: ObservableObject {  // <--- here
    @Published var itemContent = ItemContent()
    @Published var selectedRows = Set<ContentRow>()
    
    func add() {
        let row = ContentRow()
        itemContent.children.append(row)
    }
}

struct ItemContent: Identifiable, Hashable {  // <--- here
    let id = UUID()
    var children: [ContentRow] = []
}

struct ContentRow: Identifiable, Hashable {  // <--- here
    let id = UUID()
    var title = "new"
    var children: [ContentRow] = []
}

struct ContentView: View {
    @StateObject private var vm = ViewModel()
    
    var body: some View {
        NavigationStack {
            List(selection: $vm.selectedRows) {
                ForEach($vm.itemContent.children) { $row in  // <--- here
                    RowView(row: $row)
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("add", action: vm.add)
                }
            }
        }
    }
}

struct RowView: View {
    @Binding var row: ContentRow
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField("editable text", text: $row.title)
            Text(row.id.uuidString)
        }
    }
}

Для получения дополнительной информации см. также Мониторинг данных он дает вам несколько хороших примеров того, как управлять данными в вашем приложении.

Я также рекомендую использовать более новую платформу Observation, см. Управление данными модели в вашем приложении

Спасибо workdog за ссылки на Apple.

MatFetsch 03.09.2024 16:34

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