В моем текущем проекте мне трудно заставить 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 изменилось, и пользовательский интерфейс необходимо обновить, есть какие-нибудь предложения, как заставить это работать?
Попробуйте использовать 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 {....}