I'm trying to add a checkmark to every food picture the user selects, but it gets selected on every option instead of just one.
I understand it's the ForEach that may be causing this. But I can't think of a way to fix this.
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom) {
ForEach(comidas) { comida in
VStack(alignment: .center) {
ZStack(alignment: .center) {
Image(uiImage: comida.foodImage)
.resizable()
.blur(radius: 2)
.frame(width: 200, height: 200)
.cornerRadius(10)
.onTapGesture {
withAnimation {
isChecked.toggle()
}
}
Text(comida.name)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
Image(systemName: "checkmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(Color.white)
.frame(width: 100, height: 100)
.opacity(isChecked ? 1 : 0 )
.animation(.easeIn(duration: 0.5))
}
}
.padding(.horizontal, 3)
}
}
}
//"comidas" is food in Spanish
struct Comidas: Identifiable {
var id: Int
let name: String
let foodImage: UIImage
}
let comidas = [
Comidas(id: 0, name: "Asado", foodImage: UIImage(imageLiteralResourceName: "asado")),
Comidas(id: 1, name: "Pizzas", foodImage: UIImage(imageLiteralResourceName: "pizzas")),
Comidas(id: 2, name: "Milanesas", foodImage: UIImage(imageLiteralResourceName: "milanesas")),
Comidas(id: 3, name: "Empanadas", foodImage: UIImage(imageLiteralResourceName: "empanadas")),
Comidas(id: 4, name: "Pastas", foodImage: UIImage(imageLiteralResourceName: "pasta")),
Comidas(id: 5, name: "Sushi", foodImage: UIImage(imageLiteralResourceName: "sushi")),
Comidas(id: 6, name: "Facturas", foodImage: UIImage(imageLiteralResourceName: "facturas")),
Comidas(id: 7, name: "Café", foodImage: UIImage(imageLiteralResourceName: "cafe")),
Comidas(id: 8, name: "Helados", foodImage: UIImage(imageLiteralResourceName: "helados"))
]
CodePudding user response:
You could try this approach, using an ObservableObject for your comidas array,
and a var isChecked in the Comidas struct. This works well for me.
struct ContentView: View {
@StateObject var comidasModel = ComidasModel() // <-- here
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom) {
ForEach(comidasModel.comidas) { comida in // <-- here
VStack(alignment: .center) {
ZStack(alignment: .center) {
Image(uiImage: comida.foodImage)
.resizable()
.blur(radius: 2)
.frame(width: 200, height: 200)
.cornerRadius(10)
.onTapGesture {
withAnimation {
comidasModel.toggleChecked(for: comida) // <-- here
}
}
Text(comida.name)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
Image(systemName: "checkmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(Color.white)
.frame(width: 100, height: 100)
.opacity(comida.isChecked ? 1 : 0 )
.animation(.easeIn(duration: 0.5))
// enable click on the checkmark circle
.onTapGesture {
withAnimation {
comidasModel.toggleChecked(for: comida)
}
}
}
}
.padding(.horizontal, 3)
}
}
}
}
}
class ComidasModel: ObservableObject {
@Published var comidas = [
Comidas(id: 0, name: "Asado", foodImage: UIImage(imageLiteralResourceName: "asado")),
Comidas(id: 1, name: "Pizzas", foodImage: UIImage(imageLiteralResourceName: "pizzas")),
Comidas(id: 2, name: "Milanesas", foodImage: UIImage(imageLiteralResourceName: "milanesas")),
Comidas(id: 3, name: "Empanadas", foodImage: UIImage(imageLiteralResourceName: "empanadas")),
Comidas(id: 4, name: "Pastas", foodImage: UIImage(imageLiteralResourceName: "pasta")),
Comidas(id: 5, name: "Sushi", foodImage: UIImage(imageLiteralResourceName: "sushi")),
Comidas(id: 6, name: "Facturas", foodImage: UIImage(imageLiteralResourceName: "facturas")),
Comidas(id: 7, name: "Café", foodImage: UIImage(imageLiteralResourceName: "cafe")),
Comidas(id: 8, name: "Helados", foodImage: UIImage(imageLiteralResourceName: "helados"))
]
func toggleChecked(for comida: Comidas) {
if let ndx = comidas.firstIndex(of: comida) {
comidas[ndx].isChecked.toggle()
}
}
}
struct Comidas: Identifiable, Equatable {
var id: Int
var isChecked: Bool = false // <-- here
let name: String
let foodImage: UIImage
}
CodePudding user response:
Keep a reference of the selected comidas and use that to determine a comida's checked status. I prefer using a Set in this instance because it automatically handles duplicates and simplifies adding/removing an item.
First thing is to add Hashable conformance to Comida so that we can do Set operations with it. All the properties inside the Comida struct already conform to Hashable so declaring Hashable conformance is all you need.
//note that comida should be singular since it represents a single item
struct Comida: Identifiable, Hashable {
In your view, add a @State variable to track the user-selected Comidas
@State var comidasEligidas: Set<Comida> = []
In your tap gesture on the comida Image, add or remove the comida from the comidasEligidas Set
.onTapGesture {
if comidasEligidas.contains(comida) {
comidasEligidas.remove(comida)
} else {
comidasEligidas.insert(comida)
}
}
Toggle the opacity of the check mark depending on whether the Comida is present in the set, and indicate that the value for which to observe animation is the eligidasComidas Set. Also, I recommend disallowing hitTesting on the check mark so that it doesn't swallow any touches.
.opacity(comidasEligidas.contains(comida) ? 1 : 0 )
.animation(.easeIn(duration: 0.5), value: comidasEligidas)
.allowsHitTesting(false)
Here is the full code snippet:
struct ContentView: View {
struct Comida: Identifiable, Hashable {
var id: Int
let name: String
let foodImage: UIImage
}
let comidas = [
Comida(id: 0, name: "Asado", foodImage: UIImage(imageLiteralResourceName: "asado")),
Comida(id: 1, name: "Pizzas", foodImage: UIImage(imageLiteralResourceName: "pizzas")),
Comida(id: 2, name: "Milanesas", foodImage: UIImage(imageLiteralResourceName: "milanesas")),
Comida(id: 3, name: "Empanadas", foodImage: UIImage(imageLiteralResourceName: "empanadas")),
Comida(id: 4, name: "Pastas", foodImage: UIImage(imageLiteralResourceName: "pasta")),
Comida(id: 5, name: "Sushi", foodImage: UIImage(imageLiteralResourceName: "sushi")),
Comida(id: 6, name: "Facturas", foodImage: UIImage(imageLiteralResourceName: "facturas")),
Comida(id: 7, name: "Café", foodImage: UIImage(imageLiteralResourceName: "cafe")),
Comida(id: 8, name: "Helados", foodImage: UIImage(imageLiteralResourceName: "helados"))
]
@State var comidasEligidas: Set<Comida> = []
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom) {
ForEach(comidas) { comida in
VStack(alignment: .center) {
ZStack(alignment: .center) {
Image(uiImage: comida.foodImage)
.resizable()
.blur(radius: 2)
.frame(width: 200, height: 200)
.cornerRadius(10)
.onTapGesture {
if comidasEligidas.contains(comida) {
comidasEligidas.remove(comida)
} else {
comidasEligidas.insert(comida)
}
}
Text(comida.name)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
Image(systemName: "checkmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(Color.white)
.frame(width: 100, height: 100)
.opacity(comidasEligidas.contains(comida) ? 1 : 0 )
.animation(.easeIn(duration: 0.5), value: comidasEligidas)
.allowsHitTesting(false)
}
}
.padding(.horizontal, 3)
}
}
}
}
}
