20

I am storing a Int value as State in my View. When I press a button the Int increase by one. This is working fine when I print my int value.

I have now a ForEach loop, which iterates based on this Int. When I set my State on 2 by default it works fine at the beginning. However, when I increase that Int my ForEach is not called again.

I understand that State will reload my actual view. Does it only load specific parts?

Here I declare my State:

@State var s_countVenues   : Int = 2

This is the ForEach I use. It works at the beginning, however changing s_countVenues does NOT update the view.

ForEach(0..<self.s_countVenues)
{_ in
    HStack(spacing: 0)
    {
        //here comes my view
    }
}

If necessary, here I am increasing my value by one. It works, I printed the changes and if I use it inside a Label, the Label gets updated.

self.s_countVenues += 1

TL:DR:

My Int State is working. I can increase and print it inside a label. However, using it as Statement in ForEach does not call that loop again after changing.

3 Answers 3

54

from apple docs

extension ForEach where Data == Range<Int>, ID == Int, Content : View {

    /// Creates an instance that computes views on demand over a *constant*
    /// range.
    ///
    /// This instance only reads the initial value of `data` and so it does not
    /// need to identify views across updates.
    ///
    /// To compute views on demand over a dynamic range use
    /// `ForEach(_:id:content:)`.
    public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}

So, you have to use (as suggested by Apple)

struct ContentView: View {
    @State var counter = 0
    var body: some View {
        VStack {
            ForEach(0 ..< counter, id:\.self) { i in
                    Text("row: \(i.description)")
                }
            Button(action: {
                self.counter += 1
            }, label: {
                Text("counter \(counter.description)")
            })
        }
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

@davidev Asperi's answer works but don't use it, till you don't understand how it works. It will recreate ForEach every time the counter changes, which could be something you don't expect.
Okay thanks. You mean it will loop the whole ForEach again? Yours just add the newest with the new id?
@davidev. Very roughly yes. It doesn't exist any loop, ForEach is the struct, not a swift expression describing the loop
In short: use id: \.self.
@TamásSengel, if we use id: \.self the content/view is not updating when there is any change happened.
|
8

I have silenced it with ', id: .self'

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
7

It is not due to state, it is because you use ForEach constructor with constant Range, it is documented feature, so not supposed to update, so it is not updated. The simplest solution is as following - to use identifier joined to your state. It just indicates for rendering engine that ForEach is new so refresh (Tested with Xcode 11.2 / iOS 13.2)

ForEach(0..<self.s_countVenues)
{_ in
    HStack(spacing: 0)
    {
        //here comes my view
    }
}.id(s_countVenues)

1 Comment

Thanks again for your help. It works perfect. Short and perfect answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.