ForEach更新视图不​​必要

时间:2020-07-27 23:59:22

标签: swift swiftui

这是我的代码:

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,并且仍然再次创建视图。

1 个答案:

答案 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,并由父级blocksTestPageView添加新元素,父级{ {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() 绑定没有什么意义,除了预览。因为您在示例中使用了它,所以我把它留在了这里。