2

I want make placeholder custom style so i try to use the method of Mojtaba Hosseini in SwiftUI. How to change the placeholder color of the TextField?

if text.isEmpty {
            Text("Placeholder")
                .foregroundColor(.red)
        }

but in my case, I use a foreach with a Array for make a list of Textfield and Display or not the Text for simulate the custom placeholder.

          ForEach(self.ListeEquip.indices, id: \.self) { item in
     ForEach(self.ListeJoueurs[item].indices, id: \.self){idx in
// if self.ListeJoueurs[O][O] work
        if self.ListeJoueurs[item][index].isEmpty {
                                    Text("Placeholder")
                                        .foregroundColor(.red)
                                }
    }
    }

How I can use dynamic conditional with a foreach ?


Now I have a another problem :

i have this code :

struct EquipView: View {
    @State var ListeJoueurs = [
        ["saoul", "Remi"],
        ["Paul", "Kevin"]
    ]
    @State var ListeEquip:[String] = [
        "Rocket", "sayans"
    ]

    var body: some View {
        VStack { // Added this
            ForEach(self.ListeEquip.indices) { item in

                BulleEquip(EquipName: item, ListeJoueurs: self.$ListeJoueurs, ListeEquip: self.$ListeEquip)

            }
        }
    }

}


struct BulleEquip: View {
    var EquipName = 0
    @Binding var ListeJoueurs :[[String]]
    @Binding var ListeEquip :[String]

    var body: some View {
        VStack{
        VStack{
            Text("Équipe \(EquipName+1)")
        }
        VStack { // Added this
            ForEach(self.ListeJoueurs[EquipName].indices) { index in
                ListeJoueurView(EquipNamed: self.EquipName, JoueurIndex: index, ListeJoueurs: self.$ListeJoueurs, ListeEquip: self.$ListeEquip)
            }
            HStack{
            Button(action: {
                self.ListeJoueurs[self.EquipName].append("")  //problem here
            }){
                Text("button")
                }
            }
        }
    }
    }

}

struct ListeJoueurView: View {
    var EquipNamed = 0
    var JoueurIndex = 0
    @Binding var ListeJoueurs :[[String]]
    @Binding var ListeEquip :[String]

    var body: some View {
        HStack{
            Text("Joueur \(JoueurIndex+1)")
        }
    }

}

I can run the App but I have this error in console when I click the button : ForEach, Int, ListeJoueurView> 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!

Can someone enlighten me?

1 Answer 1

6

TL;DR

You need a VStack, HStack, List, etc outside each ForEach.

Updated

For the second part of your question, you need to change your ForEach to include the id parameter:

ForEach(self.ListeJoueurs[EquipName].indices, id: \.self)

If the data is not constant and the number of elements may change, you need to include the id: \.self so SwiftUI knows where to insert the new views.

Example

Here's some example code that demonstrates a working nested ForEach. I made up a data model that matches how you were trying to call it.

struct ContentView: View {

    // You can ignore these, since you have your own data model
    var ListeEquip: [Int] = Array(1...3)
    var ListeJoueurs: [[String]] = []
    // Just some random data strings, some of which are empty
    init() {
        ListeJoueurs = (1...4).map { _ in (1...4).map { _ in Bool.random() ? "Text" : "" } }
    }

    var body: some View {
        VStack { // Added this
            ForEach(self.ListeEquip.indices, id: \.self) { item in
                VStack { // Added this
                    ForEach(self.ListeJoueurs[item].indices, id: \.self) { index in
                        if self.ListeJoueurs[item][index].isEmpty { // If string is blank
                            Text("Placeholder")
                                .foregroundColor(.red)
                        } else { // If string is not blank
                            Text(self.ListeJoueurs[item][index])
                        }
                    }
                }.border(Color.black)
            }
        }
    }
}

Explanation

Here's what Apple's documentation says about ForEach:

A structure that computes views on demand from an underlying collection of of [sic] identified data.

So something like

ForEach(0..2, id: \.self) { number in
    Text(number.description)
}

is really just shorthand for

Text("0")
Text("1")
Text("2")

So your ForEach is making a bunch of views, but this syntax for declaring views is only valid inside a View like VStack, HStack, List, Group, etc. The technical reason is because these views have an init that looks like

init(..., @ViewBuilder content: () -> Content)

and that @ViewBuilder does some magic that allows this unique syntax.

Sign up to request clarification or add additional context in comments.

2 Comments

When I try to add a string at ListeJoueurs with a button " self.ListeJoueurs[item].append("one more")" in first Foreach I have "Generic parameter 'Data' could not be inferred" error, how can fix it ?
@NicolasM I added an update to my answer. You need to include the id parameter in ForEach.

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.