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?

Nicolas M
  • 475
  • 1
  • 7
  • 14

1 Answers1

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.

John M.
  • 8,892
  • 4
  • 31
  • 42
  • 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 ? – Nicolas M Sep 29 '19 at 19:14
  • 1
    @NicolasM I added an update to my answer. You need to include the id parameter in ForEach. – John M. Sep 30 '19 at 04:09