如何避免 SwiftUI 使用模型数据重绘

问题描述 投票:0回答:1

我试图更好地理解 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 仅重绘当我使用这样的模型数据时正在更改的视图。

swift swiftui
1个回答
0
投票

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
才会更新。

© www.soinside.com 2019 - 2024. All rights reserved.