View is not rerendered in Nested ForEach loop

前端 未结 2 1299
孤独总比滥情好
孤独总比滥情好 2020-12-18 20:28

I have the following component that renders a grid of semi transparent characters:

    var body: some View {
               


        
相关标签:
2条回答
  • 2020-12-18 20:46

    TL;DR

    Your ForEach needs id: \.self added after your range.

    Explanation

    ForEach has several initializers. You are using

    init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
    

    where data must be a constant.

    If your range may change (e.g. you are adding or removing items from an array, which will change the upper bound), then you need to use

    init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (Data.Element) -> Content)
    

    You supply a keypath to the id parameter, which uniquely identifies each element that ForEach loops over. In the case of a Range<Int>, the element you are looping over is an Int specifying the array index, which is unique. Therefore you can simply use the \.self keypath to have the ForEach identify each index element by its own value.

    Here is what it looks like in practice:

    struct ContentView: View {
        @State var array = [1, 2, 3]
    
        var body: some View {
            VStack {
                Button("Add") {
                    self.array.append(self.array.last! + 1)
                }
    
                // this is the key part  v--------v
                ForEach(0..<array.count, id: \.self) { index in
                    Text("\(index): \(self.array[index])")
                    //Note: If you want more than one views here, you need a VStack or some container, or will throw errors
                }
            }
        }
    }
    

    If you run that, you'll see that as you press the button to add items to the array, they will appear in the VStack automatically. If you remove "id: \.self", you'll see your original error:

    `ForEach(_:content:)` should only be used for *constant* data. 
    Instead conform data to `Identifiable` or use `ForEach(_:id:content:)`
    and provide an explicit `id`!"
    
    0 讨论(0)
  • 2020-12-18 20:55

    ForEach should only be used for constant data. So it is only evaluated once by definition. Try wrapping it in a List and you will see errors being logged like:

    ForEach, Int, TupleView<(Spacer, HStack, Int, TupleView<(Spacer, Text, Spacer)>>>, Spacer)>> count (7) != its initial count (0). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!

    I was surprised by this as well, and unable to find any official documentation about this limitation.

    As for why it is not possible for you to access the i in the inner ForEach-loop, I think you probably have a misleading compiler error on your hands, related to something else in the code that is missing in your snippets. It did compile for me after completing the missing parts with a best guess (Xcode 11.1, Mac OS 10.14.4).

    Here is what I came up with to make your ForEach go over something Identifiable:

    
    
    struct SettingsElement: Identifiable {
        var id: Int { value }
    
        let value: Int
    
        init(_ i: Int) { value = i }
    }
    
    class Settings: ObservableObject {
        @Published var rows = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
        @Published var columns = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
    }
    
    struct ContentView: View {
        @EnvironmentObject var settings: Settings
    
        func getSymbol(index: Int) -> Text { Text("\(index)") }
    
        var body: some View {
            VStack{
                Text("\(self.settings.rows.count) x \(self.settings.columns.count)")
                ForEach(self.settings.rows) { i in
                    VStack {
                        HStack {
                            ForEach(self.settings.columns) { j in
                                Text("\(i.value) \(j.value)")
                            }
                        }
                    }
                }
            }
        }
    }
    
    
    0 讨论(0)
提交回复
热议问题