Я создал небольшой образец для демонстрации проблемы. У меня есть класс 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»? Не уверен, что я уловил суть.
Строка, извините, не посмотреть
Спасибо. Я не могу удалить Hashable и Equatable из-за использования Set<ContentRow> для выбора списка. Я обновил ContentRow с помощью Observable и Publish, изменив привязку на ObservedObject в RowView. Но он по-прежнему не обновляет список.
Затем включите все свойства, а не только идентификатор. SwiftUI использует его, он буквально говорит SwiftUI рендериться только при изменении идентификатора.
Содержимое товара также необходимо соблюдать, посмотрите ссылку.
Обратите внимание: если вы перейдете на Observable, все эти слои не понадобятся.
Я добавил обновленный код, как я понял ваши комментарии и комментарии в упомянутом посте, но он все еще не обновляет список.
Вы по-прежнему не наблюдаете за содержимым элемента и не настроили реализации пользовательских протоколов.
Вам не нужно устанавливать все модели как 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> при использовании только структур. Компилятор говорит мне использовать класс, который должен быть хешируемым. Опознаваемый был случайно скопирован из моего проекта туда, где он был нужен.
Что касается проблемы выбора, вы неправильно используете API компонента List
. Просто установите это так: List($vm.itemContent.children, id: \.self, selection: $vm.selectedRows) { RowView(row: $row) }
И все готово.
не следует иметь ItemContent
с @Published
, это неправильно.
Попробуйте этот подход, создавая 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.
Добавьте ObservableObject в ContentView, добавьте Published в свойства и удалите пользовательские Hashable и Equatable. Затем избавьтесь от всего кода строки привязки и используйте ObservedObject.