Search code examples
xcodeswiftuiswiftui-foreach

Why does ForEach not work with onTapGesture modifier correctly in SwiftUI


I am trying to figure out why all the elements in a ForEach array change when only a single element is tapped.

import SwiftUI

struct SwiftUIView: View {
    
    @State var boolTest = false
    
    var nums = ["1","2","3","4","5","6","7"]
    
    var body: some View {
        VStack {
            ForEach(nums, id: \.self) { num in
                Text("\(num)")
                    .font(.system(size: 70))
                    .foregroundColor(boolTest ? .red : .green)
                    .onTapGesture {
                        boolTest.toggle()
                    }
            }
            
        }
    }
}

For example, when I tap on the view containing 1, all the other numbers also change colors. I want to be able to click on a number and have only that number change color.


Solution

  • You are using one single variable for all elements of the array nums. So, the foreground color will always be the same - they all read the same value.

    What you need is to create a subview that manages its own color independently. Separate the ForEach from its content, like this:

    struct SwiftUIView: View {
        
        // This variable should not be here: all elements read the same value
        // @State var boolTest = false
        
        var nums = ["1","2","3","4","5","6","7"]
        
        var body: some View {
            VStack {
                ForEach(nums, id: \.self) { num in
    
                    // Call another view
                    Subview(text: num)
                }
                
            }
        }
    }
    
    // Create a subview with its own variable for each element
    struct Subview: View {
    
        // Here, there is one boolTest for each element
        @State private var boolTest = false
        let text: String
    
        var body: some View {
                    Text(text)
                        .font(.system(size: 70))
                        .foregroundColor(boolTest ? .red : .green)
                        .onTapGesture {
                            boolTest.toggle()
                        }
        }
    }