iosswiftmobileswiftui

How can I make these SwiftUI text "buttons" change color on tap?


In SwiftUI, how can I make these text "buttons" change color on tap, but revert when you remove your finger?

https://i.sstatic.net/1R0OJ.jpg

Here's what the button code looks like:

    LazyVGrid(columns:
                Array(repeating:
                GridItem(.flexible(),
                spacing: 5),
                count: 2),
                spacing: 2) {
        
        ForEach(viewModel.productIngredients, id: \.self) { ingredient in
            
           Text(ingredient.name)
                .font(.system(size: 14))
                .fontWeight(.medium)
                .foregroundColor(.black)
                .padding(8)
                .background(RoundedRectangle(cornerRadius: 10).stroke(Color.black, lineWidth: 2))
                .padding(.top,5)
///             .background(self.selectedIngredient == ingredient ? Color.blue : Color.white)
                .onTapGesture {
                    self.didTap.toggle()
                    self.selectedIngredient = ingredient
                }
            }
    }

Solution

  • You can use a custom ButtonStyle to do this:

    struct ContentView : View {
        var body: some View {
            Button(action: {
                //Your action code, taken from the previous `onTapGesture` in the original code
                //didTap.toggle()
                //selectedIngredient = ingredient
            }) {
                Text("Ingredient")
                    .fontWeight(.medium)
            }.buttonStyle(CustomButtonStyle(isSelected: false)) //could pass a parameter here like isSelected: selectedIngredient == ingredient from your original code
        }
    }
    
    struct CustomButtonStyle : ButtonStyle {
        var isSelected: Bool
     
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .font(.system(size: 14))
                .foregroundColor(.black)
                .padding(8)
                .background(RoundedRectangle(cornerRadius: 10)
                                .stroke(configuration.isPressed ? Color.red : Color.black, lineWidth: 2)
                )
                .padding(.top,5)
                //Could also modify style based on isSelected
        }
    }
    

    Notice that your Text view is now wrapped in a Button and given a buttonStyle of CustomButtonStyle.

    Inside CustomButtonStyle, I use a ternary expression to set the color of the background RoundedRectangle based on configuration.isPressed.

    I also showed how you could pass in another parameter (isSelected) because in your original example it looked like you may want to do things conditionally based on that as well.


    Update with full working example showing columns:
    struct Ingredient : Identifiable, Hashable {
        var id = UUID()
        var name = "Ingredient"
    }
    
    struct ContentView: View {
        
        @State var ingredients = [Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient(),Ingredient()]
        
        var body: some View {
            LazyVGrid(columns:
                        Array(repeating:
                                GridItem(.flexible(),
                                         spacing: 5),
                              count: 2),
                      spacing: 2) {
                
                ForEach(ingredients, id: \.self) { ingredient in
                    
                    Button(action: {
                        //Your action code, taken from the previous `onTapGesture` in the original code
                        //didTap.toggle()
                        //selectedIngredient = ingredient
                    }) {
                        Text(ingredient.name)
                            .fontWeight(.medium)
                    }.buttonStyle(CustomButtonStyle(isSelected: false))
                }
            }
        }
    }
    
    struct CustomButtonStyle : ButtonStyle {
        var isSelected: Bool
        
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .font(.system(size: 14))
                .foregroundColor(.black)
                .padding(8)
                .background(RoundedRectangle(cornerRadius: 10)
                                .stroke(configuration.isPressed ? Color.red : Color.black, lineWidth: 2)
                )
                .padding(.top,5)
            //Could also modify style based on isSelected
        }
    }