В моем текущем проекте мне трудно заставить SwiftUI распознать изменение во вложенном ObservableObject, здесь демонстрационный/отладочный код.
import SwiftUI
import SwiftData
class Collection: ObservableObject, Identifiable, Hashable {
static func == (lhs: Collection, rhs: Collection) -> Bool {
return lhs.title == rhs.title && lhs.items == rhs.items
}
func hash(into hasher: inout Hasher) {
hasher.combine(title)
}
var id = UUID()
@Published var title : String = ""
@Published var items : [String] = []
@Published var isCollapsed: Bool = true
init(title: String, items: [String]) {
self.title = title
self.items = items
}
}
class GridViewModel: ObservableObject {
@Published var collections : [Collection] = []
}
struct ContentView: View {
// @EnvironmentObject var gridViewModel : GridViewModel
@StateObject var gridViewModel = GridViewModel()
var body: some View {
VStack {
Text("Toggle Visibility with observed Objects")
Button("Add Collection", action: {
addCollection()
})
ScrollView {
ForEach(gridViewModel.collections, id: \.self) { coll in
Button("toggle visibility for:", action: {
coll.isCollapsed.toggle() // does not work
})
Button("edit", action: {
changeItem()
})
Text(coll.title)
if coll.isCollapsed {
ForEach(coll.items, id: \.self) { item in
Text(item)
}
}
Spacer(minLength: 20)
}
}
}
.onAppear {
let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])
gridViewModel.collections = [collection1, collection2, collection3]
}
}
func addCollection() {
let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
gridViewModel.collections.append(collectionNew)
gridViewModel.objectWillChange.send() // does work
}
func changeItem() {
gridViewModel.collections[0].items[0] = "---edit------edit------edit---"
print("done: changed to: \(gridViewModel.collections[0].items[0])")
gridViewModel.objectWillChange.send() // works
}
}
#Preview {
var gridViewModel = GridViewModel()
return ContentView().environmentObject(gridViewModel)
#if os(macOS)
.frame(width: 700, height: 500)
#endif
}
Как видите, изменение на первом уровне, который находится внутри GridViewModel, вызывает повторный рендеринг, но изменение внутри Collection — нет.
мне нужно, чтобы SwiftUI распознал, что логическое значение isCollapsed изменилось, и пользовательский интерфейс необходимо обновить, есть какие-нибудь предложения, как заставить это работать?
это просто новый макрос, и, насколько я знаю, его поведение не должно меняться, не так ли?
Изменить коллекцию на структуру
спасибо @workingdogsupportUkraine, этот макрос Observable действительно изменил ситуацию! я не ожидал этого! Пожалуйста, опубликуйте ответ, чтобы я мог отметить его как правильный - и большое спасибо!!
Попробуйте использовать gridViewModel.objectWillChange.send()
, как показано в этом коде.
Button("toggle visibility for:", action: {
gridViewModel.objectWillChange.send() // <--- here
coll.isCollapsed.toggle()
})
РЕДАКТИРОВАТЬ-1:
Как уже упоминалось, вы также можете использовать struct Collection
включить в старый стиль class GridViewModel: ObservableObject
, например:
struct Collection: Identifiable, Hashable { //<--- here
let id = UUID()
var title: String = ""
var items: [String] = []
var isCollapsed: Bool = true
}
class GridViewModel: ObservableObject {
@Published var collections : [Collection] = []
}
struct ContentView: View {
// @EnvironmentObject var gridViewModel : GridViewModel
@StateObject private var gridViewModel = GridViewModel()
var body: some View {
VStack {
Text("Toggle Visibility with observed Objects")
Button("Add Collection") {
addCollection()
}
ScrollView {
ForEach($gridViewModel.collections) { $coll in //<--- here $
Button("toggle visibility for:", action: {
coll.isCollapsed.toggle()
})
Button("edit", action: {
changeItem()
})
Text(coll.title)
if coll.isCollapsed {
ForEach(coll.items, id: \.self) { item in
Text(item)
}
}
Spacer(minLength: 20)
}
}
}
.onAppear {
let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])
gridViewModel.collections = [collection1, collection2, collection3]
}
}
func addCollection() {
let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
gridViewModel.collections.append(collectionNew)
}
func changeItem() {
gridViewModel.collections[0].items[0] = "---edit------edit------edit---"
print("done: changed to: \(gridViewModel.collections[0].items[0])")
}
}
РЕДАКТИРОВАТЬ-2:
Как уже упоминалось в моих комментариях, я использую рекомендованные более современные Observable framework
@Observable class Collection: Identifiable { // <--- here
let id = UUID()
var title: String
var items: [String]
var isCollapsed: Bool
init(title: String, items: [String], isCollapsed: Bool = true) {
self.title = title
self.items = items
self.isCollapsed = isCollapsed
}
}
@Observable class GridViewModel { // <--- here
var collections : [Collection] = []
}
struct ContentView: View {
// when passing from parent ... .environment(gridViewModel)
// @Environment(GridViewModel.self) private var gridViewModel
@State private var gridViewModel = GridViewModel() // <---here
var body: some View {
VStack {
Text("Toggle Visibility with observed Objects")
Button("Add Collection") {
addCollection()
}
ScrollView {
ForEach(gridViewModel.collections) { coll in
Button("toggle visibility for:", action: {
coll.isCollapsed.toggle()
})
Button("edit", action: {
changeItem()
})
Text(coll.title)
if coll.isCollapsed {
ForEach(coll.items, id: \.self) { item in
Text(item)
}
}
Spacer(minLength: 20)
}
}
}
.onAppear {
let collection1 = Collection(title: "first Collection", items: ["apple", "banana", "citrus"])
let collection2 = Collection(title: "second Collection", items: ["banana", "citrus", "apple"])
let collection3 = Collection(title: "third Collection", items: ["citrus", "banana", "apple"])
gridViewModel.collections = [collection1, collection2, collection3]
}
}
func addCollection() {
let collectionNew = Collection(title: "new_vierte Collection", items: ["apple", "banana", "citrus"])
gridViewModel.collections.append(collectionNew) //<--- here
}
func changeItem() {
gridViewModel.collections[0].items[0] = "---edit------edit------edit---" //<--- here
print("done: changed to: \(gridViewModel.collections[0].items[0])")
}
}
Обратите внимание: не используйте static func == ...
и func hash...
, удалите их.
Также обратите внимание: использование ForEach(coll.items, id: \.self)
является плохой практикой,
убедитесь, что ваш coll.items
не содержит несколько одинаковых строк,
используя, например, struct ItemName: Identifiable {....}
используйте @Observable вместо ob ObservableObject, исправив проблему: @Observable class Collection: Identifiable, Hashable { static func == (lhs: Collection, rhs: Collection) -> Bool { return lhs.title == rhs.title && lhs.items == rhs.items } func hash(into hasher: inout Hasher) { hasher.combine(title) } var id = UUID() var title : String = "" var items : [String] = [] var isCollapsed: Bool = true init(title: String, items: [String]) { self.title = title self.items = items } }
обновил свой ответ, используя @Observable
, как я уже упоминал в своих комментариях.
Спасибо большое (с)
Те же старые вещи, вложение
ObservableObject
, как вы (Коллекция внутри GridViewModel), не является хорошей идеей. При использовании вложенных ObservableObjects внутренний объект не вызывает обновление внешнего представления. Вам нужно сделать это вручную самостоятельно. Как насчет использования более современного Observable.