我试图更好地理解 SwiftUI 在使用可观察对象作为模型数据时如何重绘。
听一个例子。我的 InputView 中有
arc4random_uniform
,这样我就可以看到重绘何时发生。这正如我所期望的那样,并且仅重绘键入时聚焦的视图。
struct InputView: View {
@Binding var text: String
var body: some View {
HStack {
TextField("", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("\(arc4random_uniform(10000))")
}
.padding()
}
}
struct ContentView: View {
@State var text1: String = ""
@State var text2: String = ""
@State var text3: String = ""
var body: some View {
VStack {
InputView(text: $text1)
InputView(text: $text2)
InputView(text: $text3)
}
}
}
但是,如果我尝试使用保存绑定字符串的可观察对象数组执行相同的操作,当您输入一个视图时,它会重新绘制所有视图。
class Document: ObservableObject {
@Published var items: [Item] = []
init(items: [Item]) {
self.items = items
}
class Item: ObservableObject, Identifiable {
@Published var text: String = ""
let id = UUID()
}
}
struct InputView: View {
@Binding var text: String
var body: some View {
HStack {
TextField("", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("\(arc4random_uniform(10000))")
}
.padding()
}
}
struct ContentView: View {
@StateObject var document = Document(items: [.init(), .init(), .init()])
var body: some View {
VStack {
ForEach($document.items) { $item in
InputView(text: $item.text)
}
}
}
}
我不确定如何让 SwiftUI 仅重绘当我使用这样的模型数据时正在更改的视图。
SwiftUI 根据依赖图更新视图。当视图所依赖的事物发生变化时,SwiftUI 会更新该视图。在第二个示例中,每个
InputView
都取决于已发布的属性 document.items
。 SwiftUI 知道这一点,因为您将 $document.items
传递给了 ForEach
。因此,当 document.items
发布值时,所有 InputView
都会更新。
为什么 SwiftUI 没有看到每个
InputView
依赖于 document
的不同关键路径?嗯,它当然可以那样设计,但是ObservableObject
并不是为了这样做而设计的。你一开始就不应该像这样“筑巢”ObservableObject
。
@Observable
为 SwiftUI 提供了更细粒度的方式来观察变化,从而构建更细粒度的依赖关系图。这是您使用 @Observable
而不是 ObservableObject
的代码,它按照您期望的方式工作。每个 Item
对象的 text
都会单独跟踪。
@Observable
class Document {
var items: [Item] = []
init(items: [Item]) {
self.items = items
}
@Observable
class Item: Identifiable {
var text: String = ""
let id = UUID()
}
}
struct InputView: View {
@Binding var text: String
var body: some View {
HStack {
TextField("", text: $text)
.textFieldStyle(.roundedBorder)
Text("\(Int.random(in 0..<10000))")
}
.padding()
}
}
struct ContentView: View {
@State var document = Document(items: [.init(), .init(), .init()])
var body: some View {
VStack {
ForEach(document.items) { item in
@Bindable var bindable = item
InputView(text: $bindable.text)
}
}
}
}
equatable()
。您可以使 InputView
符合 Equatable
:
struct InputView: View, Equatable {
@Binding var text: String
static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.text == rhs.text
}
var body: some View {
...
}
}
然后使用时添加
equatable()
修饰符:
ForEach($document.items) { $item in
InputView(text: $item.text)
.equatable()
}
现在,仅当您在其父级更新后创建的
InputView
与其父级更新前的 InputView
不同(由 ==
确定)时,InputView
才会更新。