0

For some reason I can't really understand how ForEach works in Swift and I can't see where the ID that is required is really useful could someone maybe give me an example?

var emojis = ["a","a"]
var body: some View {
        
        VStack
        {
            ScrollView
            {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 85))])
                {
                    ForEach(emojis[0..<emojiCount], id: \.self){ emoji in
                        CardView(content: emoji).aspectRatio(2/3, contentMode:  .fill)
                   }
                    
                    
                }
                
                
            }
            .foregroundColor(.red)
            Spacer()
            HStack
            {
                remove
                Spacer()
                add
                
            }
            .padding(.horizontal)
            .font(.largeTitle)
            
        }
        .padding(.horizontal)
        
    }
    
    var remove : some View
    {
        Button{
            
            if emojiCount<emojis.count
            {
                emojiCount += 1
            }
            
        } label:{
            Image(systemName: "plus.diamond")
            
        }
    }
    var add : some View
    {
        
        Button{
            if emojiCount>1
            {
                emojiCount -= 1
            }
            
        } label:{
            Image(systemName: "minus.diamond")
            
        }
        
    }

}



struct CardView: View{
    @State var isFaceUp: Bool = true
    var content : String
    
    
    
    var body: some View{
        
        
        ZStack {
            let shape = RoundedRectangle(cornerRadius:20)
            
            if isFaceUp
            {
                
                shape.fill().foregroundColor(.white)
                shape.strokeBorder(lineWidth: 3)
                Text(content).font(.largeTitle)
                
                
                
            }

Like here for example(I know the code isn't complete and I know that this is really bad code but this is from a video) when I click on one CardView because both of them have the same ID both cards flip over even though I only clicked on one. But why is exactly is this happening? I guess I just don't fully understand how ForEach really works but to me it seems like a for loop and if it's just iterating over the emoji array and making a cardview for each string 1. Why is an ID necessary? 2. Why does clicking only one CardView flip them both over? I mean I for loop over the array and make a CardView for emojis[0] and another for emojis[1] they are both 2 different CardView's aren't they?

praivis
  • 77
  • 4
  • I would make the comparison to a tableView. the id is the cell index. If you select a cell and send that 2 indexes have been tapped, then 2 events will happen. – Vollan Jun 30 '22 at 07:08

1 Answers1

1

ForEach uses the ID of each item to (hopefully) uniquely identify each subview, so it can determine whether an item was added/removed or has remained the same. SwiftUI then determines how to react to this. That's why a stable ID is needed that's unique among the set you want to present: you get unexpected results if the ID is not stable or is not unique (like two card views changing instead of just one, as you've experienced).

Instead of specifying the ID manually via ForEach(collection, id: \.keyPath), you can also make your collection elements conform to Identifiable. Then you can just write ForEach(collection).

DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • But what exactly is happening in this case? I mean this isn't just a for loop then but it still seems like one because it's looping over all array elements then creating a LazyVGrid with all the CardViews with the content of array elements. Also you're saying that the ID is to identify the subviews? And not the array elements themselves? – praivis Jun 30 '22 at 07:31
  • Basically, it is a loop that applies some extra magic to the SwiftUI views returned in its body (the block where you return your `CardView(…)`) and adds them to the parent view of `ForEach`. Those added views are assigned IDs (generated from the IDs of your collection elements) so SwiftUI can detect and handle changes of the view hierarchy correctly (search for "identity in SwiftUI" to gain more insight into this subject). – DarkDust Jun 30 '22 at 07:38