【问题标题】:How can I prevent re-Initializing ForEach in SwiftUI?如何防止在 SwiftUI 中重新初始化 ForEach?
【发布时间】:2021-02-21 12:53:05
【问题描述】:

我有一个非常简单的代码,我希望它尽可能简单,我正在使用 ForEach 来呈现一些简单的文本,为了了解正在秘密发生的事情,我制作了一个 TextView 以在每次调用此视图时得到通知通过 SwiftUI,不幸的是,每次我向数组中添加新元素时,SwiftUI 都会渲染从乞求到结束的所有数组元素,我想要并期望它仅为新元素调用 TextView,所以有一种方法来定义 可以解决问题的视图/文本数组,但是对于这样一个简单的工作来说,这太过分了,我的意思是我和你会在我们的项目中挑衅地使用 ForEach,我们可以在 ForEach 中使用一个简单的 Text 或任何其他自定义视图,我们如何解决这个问题以阻止 SwiftUI 一次又一次地初始化相同的东西,记住这一点,我只想使用一个简单的 String 数组而不是疯狂地定义一个 查看数组。

我的目标是使用一个简单的字符串数组来完成这项工作,而不必担心重新初始化问题。

也许是时候重新考虑在您的应用中使用 ForEach 了!

即使更新数组元素,SwiftUI 也会陷入重新渲染陷阱!这很有趣。因此,如果您有 50 行、100 行或 1000 行并且您只是更新 1 行,请做好准备,swiftUI 会重新渲染整个数组,无论您使用的是简单的 Text 还是 CustomView。所以我希望 SwiftUI 能够聪明地不再渲染所有数组,而只是进行必要的渲染以防万一。

import SwiftUI

struct ContentView: View {
    
    @State private var arrayOfString: [String] = [String]()
    
    var body: some View {
        
        ForEach(arrayOfString.indices, id:\.self) { index in
            
            TextView(stringOfText: arrayOfString[index])

        }
        
        Spacer()
        
    Button("append new element") {
        
        arrayOfString.append(Int.random(in: 1...1000).description)
    }
    .padding(.bottom)
    
    Button("update first element") {

        if arrayOfString.count > 0 {
            
            arrayOfString[0] = "updated!"
            
        }
    }
    .padding(.bottom)
 
    }
    
}

struct TextView: View {
    
    let stringOfText: String
    
    init(stringOfText: String) {
        self.stringOfText = stringOfText
        print("initializing TextView for:", stringOfText)
    }
    
    var body: some View {
        
        Text(stringOfText)
        
    }

}

【问题讨论】:

    标签: swiftui


    【解决方案1】:

    初始化和渲染不是一回事。视图会被初始化,但不一定会重新渲染。

    用你原来的ContentView试试这个:

    struct TextView: View {
        
        let stringOfText: String
        
        init(stringOfText: String) {
            self.stringOfText = stringOfText
            print("initializing TextView for:", stringOfText)
        }
        
        var body: some View {
            print("rendering TextView for:", stringOfText)
            return Text(stringOfText)
        }
    
    }
    
    

    您会看到,虽然视图被初始化,但它们实际上并没有被重新渲染。

    如果您返回 ContentView,并为每个元素添加动态 ID:

    TextView(stringOfText: arrayOfString[index]).id(UUID()) 
    

    您会看到,在这种情况下,它们实际上确实被重新渲染。

    【讨论】:

      【解决方案2】:

      你总是从索引 0 开始迭代,所以这是一个预期的结果。如果您希望 forEach 只对新添加的项目执行,则需要指定正确的范围。检查下面的代码-:

      import SwiftUI
      
      struct ContentViewsss: View {
          
          @State private var arrayOfString: [String] = [String]()
          
          var body: some View {
              
              if arrayOfString.count > 0 {
                  ForEach(arrayOfString.count...arrayOfString.count, id:\.self) { index in
                      
                      TextView(stringOfText: arrayOfString[index - 1])
                      
                  }
              }
              
              Spacer()
              
              Button("append new element") {
                  
                  arrayOfString.append(Int.random(in: 1...1000).description)
              }
              
          }
          
      }
      
      struct TextView: View {
          
          let stringOfText: String
          
          init(stringOfText: String) {
              self.stringOfText = stringOfText
              print("initializing TextView for:", stringOfText)
          }
          
          var body: some View {
              
              Text(stringOfText)
              
          }
          
      }
      

      【讨论】:

      • 谢谢,但我想你没有理解我的问题,我不想只渲染最后一个或新的文本!我想全部渲染,并在每个新的添加过程中停止重新渲染
      • 当您通过添加重新渲染时,会发生主体刷新,如果您的 for 循环从索引 0 开始,它肯定会遍历所有项目。我想你无法避免它。但是@Asperi 的解决方案将在您滚动屏幕后帮助您节省内存,因为它会将屏幕外单元格出列并重用它。
      • 我知道你说的节省内存是什么意思,但正如我所说,我想基本上解决这个问题,而不是降低使用 ForEach 的费用,正如我所说,这个问题可以通过定义一个 View 数组来解决而不是String数组,但是为什么SwiftUI不聪明的停止重新渲染问题
      • 根据我的理解(可能我不正确),这就是为什么 SwiftUI 中的视图是结构的。 struct 的大小是它所持有的任何东西,没有别的,不需要基类初始化。简而言之,它非常快,而且 swiftUI 仍然提供许多内存自定义,就像使用 LazyVStack 和其他的一样,甚至更好。
      • 我们在我的问题中面临的问题甚至与渲染的 Lazy 无关,这完全是关于停止渲染已经渲染的视图!另外正如我所说,如果我们定义一个 View 数组,SwiftUI 会理解并修复它,但是使用 Simple String 数组,SwiftUI 会很容易陷入重新渲染的陷阱。 Lazy 主题会在我们获得观点或字符串的基调时发挥作用,但在我的情况下,这不是问题。
      【解决方案3】:

      你需要在这里使用LazyVStack

      LazyVStack {
          ForEach(arrayOfString.indices, id:\.self) { index in
              TextView(stringOfText: arrayOfString[index])
          }
      }
      

      所以它会重用超出可见区域的视图。

      还要注意 SwiftUI 经常在这里和那里创建视图,因为它们只是值类型,我们不应该在自定义视图中添加任何繁重的东西 init

      重要的是不要在视图不可见或未更改时重新渲染视图,而这正是您应该考虑的事情。首先是通过Lazy*容器解决,其次是通过Equatable协议(详情见下https://stackoverflow.com/a/60483313/12299030

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多