我正在尝试构建一个View
,它是一个对象的子列表,我希望这些子列表是可修改的。数据改变了,但是视图没有反应。
下面是简化后的示例。
class ObjectOne: ObservableObject {
@Published var children: [ObjectTwo]
init() {
self.children = []
}
}
class ObjectTwo: ObservableObject {
@Published var value: Value
var id = UUID()
init(value: Value) {
self.value = value
}
}
struct ContentView: View {
@ObservedObject var object: ObjectOne = ObjectOne()
var body: some View {
VStack {
Button {
self.object.children.append(ObjectTwo(value: .one))
} label: {
Text("Add Object")
}
Text("Objects:")
ForEach(self.object.children, id: .id) { object in
HStack {
Text(String(object.value.rawValue))
Spacer()
Button {
if object.value == .one {
object.value = .two
} else {
object.value = .one
}
} label: {
Text("Toggle")
}
}
}
}
}
}
添加对象工作,列表更新,但修改ObjectTwo
的value
不会更新视图。
当您嵌套ObservableObject
时,您需要手动告诉它们即将发生的更改。试试这个,对我有用。
Button {
self.object.objectWillChange.send() // <--- here
if object.value == .one {
object.value = .two
} else {
object.value = .one
}
} label: {
Text("Toggle")
}
我建议你用struct
代替ObjectTwo
,这更容易使用(只需要一些小的代码更改),也是我的常用做法。
例如,你可以这样写:
struct ObjectTwo: Identifiable { // <-- here
var value: Value
var id = UUID()
init(value: Value) {
self.value = value
}
}
struct ContentView: View {
@StateObject var objectModel = ObjectOne() // <-- here
var body: some View {
VStack {
Button {
objectModel.children.append(ObjectTwo(value: .one))
} label: {
Text("Add Object")
}
Text("Objects:")
ForEach($objectModel.children, id: .id) { $object in // <-- here
HStack {
Text(String(object.value.rawValue))
Spacer()
Button {
if object.value == .one {
object.value = .two
} else {
object.value = .one
}
} label: {
Text("Toggle")
}
}
}
}
}
}
Swift被设计成使用值类型而不是对象(为了防止一致性错误),参见在结构和类之间进行选择- Apple Developer。SwiftUI在实现依赖和变更跟踪时利用了这些值语义,如果你使用对象,你将失去这些特性。一般来说,在SwiftUI中,我们可以使用一个存储对象(用于持久化/同步模型数据)并使用模型结构体,这样我们就可以利用SwiftUI对值类型的依赖跟踪和写访问的@Binding
,例如
class Store: ObservableObject {
static var shared = Store()
static var preview = Store(preview: true)
@Published var items: [Item] = []
init(preview: Bool) {
if (preview) {
items = [Item(text: "Test")]
}
else {
load()
}
}
func load(){}
func save(){}
}
class Item: Identifiable {
let id = UUID()
@Published title = ""
init(title: String) {
self.title = title
}
}
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(Store.shared)
}
}
}
struct ContentView: View {
@EnvironmentObject var store: Store
var body: View {
List($store.items) { $item in
TextField("Title", text: $item.title)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Store.preview)
}
}