How to correctly use index in forEach?

struct foreachTestinDubled: View {
    
    @State private var data: [String] = ["1", "2"]
 
    var body: some View {
       
        ZStack {
            ForEach(Array(data.enumerated()), id: \.element) { item, instance in
               
                tst(data: $data, item: item)
                    
            }
        }
    }
}

#Preview {
    foreachTestinDubled()
}


struct tst: View {
    
    @Binding var data: [String]
    
    let item: Int
    
    @State private var offSet: CGSize = .zero
    
    var body: some View {
        
        let tr = (data.count - 1) - item
        
        let last = CGFloat(1 - CGFloat(Double(tr ) * 0.09))
        
//        if data.indices.contains(item ) {
            
            
            Text(data[item])
                .font(.largeTitle)
                .foregroundColor(.black)
                .frame(width: 150, height: 200)
                .border(.black, width: 2)
                .background(Color.gray.gradient)
                .offset(x: CGFloat(tr * 30))
            
                .scaleEffect(CGFloat(last) )
                .offset(x: offSet.width)
                .onTapGesture {
                    data.removeLast()
                }
//                .gesture(
//                    DragGesture()
//                        .onChanged { value in
//                            offSet =  value.translation
//                        }
//                        .onEnded { value in
//                            if offSet.width > 100 {
//                                data.removeLast(1)
//                            }
//                        }
//                        
//                )
//            
//        } else {
//           let _ =  print("Else is working but Rectangle not showing ",  data.count)
//            Rectangle()
//        }
    }
}


I want to understand why, when I tap on the text, I get the error 'index out of range'? I have been using .indices before .enumerated(), but both of them cause the same error 'index out of range'.

Also, the commented if statement will make .indices and .enumerated() work perfectly, but I don't understand how?? Even though the statement falls and else executes the print statement, the rectangle won't appear (everything else works normally, even the animation). WHY? Is this something to do with the diffing?

My understanding and (please correct me or tell me if I'm correct) is that ForEach produces two view instances of tst, each with its own properties and body. So when I delete the last element of the array, the last view instance of the 2 view that foreach created its binding property notifies the @State property and then deletes itself because it is the last element (the last view of the 2 instance tst view foreach created). So when ForEach runs again, it compares the old value with the new? index out of range?

To sum up my questions:

  1. Why does it say 'index out of range'? even though the id is not the index

  2. Why does it work with the if statement, even though it falls without showing the rectangle?"

@coner

Overview

SwiftUI documentation

It might help to go through the fundamentals of SwiftUI which is declarative.

Brief explanation of ForEach

To answer your question briefly:

  • ForEach goes through all elements in an ordered collection
  • It automatically unpacks each element, so you don't access them using index
  • Each element in the collection needs to conform to identifiable

Given below is a very crude example of modifications made to your code.
This is just to get you started, however please learn the fundamentals that will help you save time on the long run

Some issues with the code

  • Access data directly, don't create an Array(data) which is a completely different copy of the data

Crude fix:

import SwiftUI

struct CardStack: View {
    
    // Since String doesn't conform to identifiable I have used the entire String 
    // as the way to identify an element
    // So these cardTitle strings need to be unique
    // If you wish to have the same titles create a type that conforms to identifiable
    @State private var cardTitles = ["1", "2"]
    
    var body: some View {
        ZStack {
            ForEach(cardTitles.enumerated(), id: \.element) { index, title in
                CardView(title: title)
                    .offset(x: Double(index * 10), y: Double(index * 10))
                    .onTapGesture {
                        cardTitles.removeLast()
                    }
            }
        }
    }
}

struct CardView: View {
    let title: String
    
    var body: some View {
        Text(title)
            .font(.largeTitle)
            .foregroundColor(.black)
            .frame(width: 150, height: 200)
            .border(.black, width: 2)
            .background(Color.gray.gradient)
    }
}