如何防止在SwiftUI中重新初始化ForEach?

如何防止在SwiftUI中重新初始化ForEach?,swiftui,Swiftui,我有一个非常简单的代码,我想让它尽可能简单,我使用ForEach来呈现一些简单的文本,为了了解秘密发生了什么,我制作了一个TextView,以便在每次SwiftUI调用该视图时得到通知,不幸的是,每次我向数组中添加新元素时,SwiftUI将从begging到end呈现所有数组元素,我希望并期待它为新元素调用TextView,因此有一种方法可以定义视图/文本的数组,这将解决问题,但对于这样一个简单的工作来说,这是过分的,我的意思是我和您将在我们的项目中大胆地使用ForEach,我们可以在ForEa

我有一个非常简单的代码,我想让它尽可能简单,我使用ForEach来呈现一些简单的文本,为了了解秘密发生了什么,我制作了一个TextView,以便在每次SwiftUI调用该视图时得到通知,不幸的是,每次我向数组中添加新元素时,SwiftUI将从begging到end呈现所有数组元素,我希望并期待它为新元素调用TextView,因此有一种方法可以定义视图/文本的数组,这将解决问题,但对于这样一个简单的工作来说,这是过分的,我的意思是我和您将在我们的项目中大胆地使用ForEach,我们可以在ForEach或任何其他自定义视图中使用一个简单的文本,我们如何解决这个问题来停止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)
 
    }
    
}
也许是时候重新考虑在应用程序中使用ForEach了

即使更新数组元素,SwiftUI也会陷入重新渲染陷阱!这很有趣。因此,请做好准备,如果您有50行、100行或1000行,并且只更新了一行,swiftUI将重新呈现整个数组,无论您使用的是简单文本还是自定义视图。因此,我希望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)
 
    }
    
}


您总是从索引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)
        
    }
    
}

您需要在此处使用
LazyVStack

LazyVStack {
    ForEach(arrayOfString.indices, id:\.self) { index in
        TextView(stringOfText: arrayOfString[index])
    }
}
所以它可以重用超出可见性区域的视图

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


重要的是不要在视图不可见或未更改时重新渲染视图,这正是您应该考虑的。第一个是通过
Lazy*
容器解决的,第二个是通过
equalable
协议解决的(详见下一步)

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

使用原始的
内容视图尝试此操作:

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()) 

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

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