SwiftUI添加到ForEach数组而不会导致整个视图重新加载

时间:2020-07-19 19:37:02

标签: swift struct foreach swiftui swiftui-list

如果我有这样的东西:

struct ContentView: View {
    var results = [Result(score: 8), Result(score: 5), Result(score: 10)]

    var body: some View {
        VStack {
            ForEach(results, id: \.id) { result in
                Text("Result: \(result.score)")
            }
        }
    }
}

然后我有一个按钮,该按钮将sometihng附加到结果数组,整个ForEach循环将重新加载。这是有道理的,但我想知道是否有某种方法可以防止这种情况。我问的原因是因为我有一个ForEach循环,其中包含一些项目,每个项目都播放一个动画。但是,如果将另一个项目附加到数组,则新项目将出现在ForEach的顶部,但是由于重新加载了整个视图,因此项目中播放的其他动画将停止。

有什么办法可以防止这种情况?是否想将项目添加到ForEach数组中并显示出来,但不重新加载整个 ForEach循环?

我认为没有,但是我想知道如何解决这个问题。

2 个答案:

答案 0 :(得分:2)

创建单独的视图来迭代ForEach内容,然后SwiftUI呈现引擎将更新容器(通过添加新项),但不会刷新现有行

ForEach(results, id: \.id) {
    ResultCellView(result: $0)
}

struct ResultCellView: View {
   let result: Result
   var body: some View {
      Text("Result: \(result.score)")
   }
}

注意:我看不到您的模型,因此可能需要将其确认为Hashable,Equatable。

答案 1 :(得分:1)

通常,不提供id会使ForEach无法知道更改了什么(因为它没有项目的跟踪),因此不会重新呈现视图。

例如

struct ContentView: View {
    
    @State var myData: Array<String> = ["first", "second"]
    
    var body: some View {
        VStack() {
            
            ForEach(0..<self.myData.count) { item in
                Text(self.myData[item])
            }
            
            Button(action: {
                self.myData.append("third")
            }){
                Text("Add third")
            }
        }
    }
}

这会抛出一个控制台输出(您可以忽略),它告诉您我刚刚在上面写的内容:

ForEach<Range<Int>, Int, Text> count (3) != its initial count (2). 
`ForEach(_:content:)` should only be used for *constant* data. 
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` 
and provide an explicit `id`!

对于您的代码,请尝试以下操作: 在iOS 13.5上进行了测试。

struct ContentView: View {
    
    @State var results = [Result(score: 8), Result(score: 5), Result(score: 10)]

    var body: some View {
        VStack {
            ForEach(0..<self.results.count) { item in
                // please use some index test on production
                Text("Result: \(self.results[item].score)")
            }
            
            Button(action: {
                self.results.append(Result(score: 11))
            }) {
                Text("Add 11")
            }
        }
    }
}

class Result {
    
    var score: Int
    
    init(score: Int) {
        self.score = score
    }

}

请注意,这是一个“ hacky”解决方案,ForEach不适用于此类情况。 (请参阅控制台输出)