这是我的代码:
struct TestPageView: View {
@State var blocks: [String] = ["1", "2", "3"]
var body: some View {
VStack {
ForEach(blocks, id: \.self) { block in
TestView(block, $blocks)
}
}
}
}
struct TestView: View {
@Binding var blocks: [String]
let block: String
init(_ block: String, _ blocks: Binding<[String]>){
print("Created empty view")
self.block = block
self._blocks = blocks
}
var body: some View {
TextField("Test view", text: .constant(block), onCommit: {
print("Committed")
blocks.append("new element")
})
}
}
基本思想是我的ForEach在我的块上循环显示它们。当用户在关注某个区块的情况下执行操作时(此处为onCommit
),我需要将一个新的区块添加到列表中。这段代码实际上正确地做到了;问题在于性能。使用上面的打印语句,输出如下:
Created empty view
Created empty view
Created empty view
Committed
Created empty view
Created empty view
Created empty view
Created empty view
因此,ForEach似乎重新渲染了列表中的所有项目。在这个简单的示例中,这不是问题,但是在我的实际问题中,重新创建所有TestView
是一项艰巨的任务,并且完全不必要:我只需要添加新元素,就可以保留其他所有内容。相同。有什么好方法吗?
注意:\.self
不是问题。我尝试使“块”符合Identifiable
。 ForEach仍然无法识别相同的ID,并且仍然再次创建视图。
答案 0 :(得分:2)
不幸的是,SwiftUI发生了很多黑盒行为,不幸的是没有足够的文档。
在较高的层次上,SwiftUI尝试查看需要无效并重新计算哪个视图的主体。主体重新渲染是一项相当昂贵的操作,因此SwiftUI仅尝试在需要时执行此操作。
话虽这么说,init
应该非常便宜!在SwiftUI构建和比较视图树时,视图一直都在初始化。
您应该查看的是计算主体的次数,而不是调用init
的次数。
碰巧,TestView
的身体 每次都会被重新计算。为什么?其原因几乎可以肯定是由于@Binding var blocks
。
更改任何@State
或@Binding
或@ObservedObject
时,SwiftUI都会重新计算主体。因此,拥有@Binding var blocks
告诉SwiftUI TestView
取决于该状态,并且当blocks
数组被更新时,它将使ForEach中的所有TestView
失效。
您可以做的是将onCommit
闭包传递给TestView
,并由父级blocks
向TestPageView
添加新元素,父级{ {1}}:
blocks
struct TestView: View {
var block: String
var onCommit: ((String) -> Void)? = nil
var body: some View {
TextField("Test view", text: .constant(self.block), onCommit: {
self.onCommit?(self.block)
})
}
}
不幸的是,这(对我来说令人惊讶)也无法解决问题,因为出于某种原因,SwiftUI确定具有关闭属性意味着应该重新计算视图。
幸运的是,有一种方法可以最终使SwiftUI确信我们知道视图实际上不需要按照// in TestPageView
ForEach(blocks, id: \.self) { block in
TestView(block: block) { txt in
self.blocks.append(txt)
}
}
进行重新计算,在此我们可以告诉SwiftUI如何比较视图更改。
Equatable
并在使用视图时使用extension TestView: Equatable {
static func == (lhs: TestView, rhs: TestView) -> Bool {
lhs.block == rhs.block
}
}
修饰符:
.equatable
仅供参考,使用TestView(block: block) { txt in
self.blocks.append(txt)
}.equatable()
绑定没有什么意义,除了预览。因为您在示例中使用了它,所以我把它留在了这里。