ForEach - 索引超出范围?

jos*_*ori 2 swiftui

为什么运行此代码会显示“致命错误:索引超出范围”?

import SwiftUI

struct MyData {
    var numbers = [Int](repeating: 0, count: 5)
}

@main
struct TrySwiftApp: App {
    @State var myData = MyData()

    var body: some Scene {
        WindowGroup {
            ChildView(myData: myData)
                .frame(width: 100, height: 100)
                .onAppear {
                    myData.numbers.removeFirst() // change myData
                }
        }
    }
}

struct ChildView: View {
    let myData: MyData // a constant

    var body: some View {
        ForEach(myData.numbers.indices) {
            Text("\(myData.numbers[$0])") // Thread 1: Fatal error: Index out of range
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

检查其他问题后,我知道我可以通过以下方式修复它

// fix 1: add id
ForEach(myData.numbers.indices, id: \.self) {
  //...
}
Run Code Online (Sandbox Code Playgroud)

或者

// Edited:
//
// This is not a fix, see George's reply
//
// fix 2: make ChildView conforms to Equatable
struct ChildView: View, Equatable {
    static func == (lhs: ChildView, rhs: ChildView) -> Bool {
        rhs.myData.numbers == rhs.myData.numbers
    }
    
    ...
Run Code Online (Sandbox Code Playgroud)

我的问题: 常量值
(由 let 定义) 如何不同步? ForEach 到底做了什么?

use*_*ser 8

让我举一个简单的例子来告诉你发生了什么:

struct ContentView: View {
    
    @State private var lowerBound: Int = 0
    
    var body: some View {
        
        ForEach(lowerBound..<11) { index in
            Text(String(describing: index))
        }
        
        Button("update") { lowerBound = 5 }.padding()
 
    }
    
}
Run Code Online (Sandbox Code Playgroud)

如果你看上面的代码,你会发现我正在用这样的 Range 初始化 ForEach:lowerBound..<11这意味着这个0..<11,当你这样做时,你是在告诉 SwiftUI,嘿,这是我的范围,它不会改变!这是一个恒定的范围!SwiftUI 说可以!如果您不打算更新上限或下限,您可以使用 ForEach 而不显示或给出id!但如果你再次看到我的代码!我正在更新 ForEach 的 lowerBound,通过此操作,我打破了关于恒定范围的协议!所以 SwiftUI 来告诉我们,如果你要更新我的 ForEach 范围的计数或任何东西,那么你必须使用 anid然后你可以更新给定的范围!原因是因为如果我们有 2 个具有相同值的相同项目,SwiftUI 将无法知道您说的是哪一个!通过使用id我们正在解决 SwiftUI 的识别问题!关于 id 你可以像这样使用它:或者如果你的结构符合可哈希协议,那么你可以id:\.self像这样使用它,或者在最后一种情况下,如果你的结构符合可识别协议,你可以停止使用 id!id:\.customID那么 ForEach 就会神奇地随之沉没。

现在看看编辑后的代码,它将构建并运行,因为我们解决了识别问题:

struct ContentView: View {
    
    @State private var lowerBound: Int = 0
    
    var body: some View {
        
        ForEach(lowerBound..<11, id:\.self) { index in
            Text(String(describing: index))
        }
        
        Button("update") { lowerBound = 5 }.padding()
 
    }
    
}
Run Code Online (Sandbox Code Playgroud)

  • 你的解释真的很容易理解。 (2认同)