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 Comida
s
@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)
}
}
}
}
}