I have a strange behavior in my code. It's a simple app that shows four views (CardView) and each time one of them is tapped, the view will reflect it by changing its border (using "selected" property). The problem is that when "value" property is a String it doesn't work. However when "value" property is of type Int it does work. What is this behavior due to?
import SwiftUI
struct Deck {
var cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map {
// Card(value: $0)
Card(value: "\($0)")
}
}
struct Card: Identifiable {
let id = UUID()
// let value: Int // Using int value solves the problem...
let value: String // Using string value provokes card view not updated
var selected = false
}
extension Card: Equatable {
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.id == rhs.id
}
}
final class TestVM: ObservableObject {
@Published var deck = Deck()
func toggleSelection(card: Card) {
let idx = deck.cards.firstIndex(of: card)!
deck.cards[idx].selected.toggle()
}
}
struct CardView: View {
@ObservedObject var testVM: TestVM
let card: Card
var body: some View {
Text("\(card.value)")
.frame(width: 40, height: 80)
.background {
Rectangle()
.stroke(card.selected ? .red : .black, lineWidth: 2)
}
.background(card.selected ? .red.opacity(0.2) : .gray.opacity(0.2))
.onTapGesture {
testVM.toggleSelection(card: card)
}
}
}
struct TestZStackView: View {
@StateObject var testVM = TestVM()
var body: some View {
HStack {
ForEach(testVM.deck.cards.prefix(4)) { card in
CardView(testVM: testVM, card: card)
}
}
}
}
struct TestZStackView_Previews: PreviewProvider {
static var previews: some View {
TestZStackView()
}
}
Inside CardView
you display card
that is declared as let card: Card
,
meaning it will not change. When testVM.toggleSelection(card: card)
is called
the array of Card
in Deck
has not really changed, that is, no cards has been added or removed from it.
So the view is not updated.
Try this approach, works for me:
struct Deck {
var cards = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map {Card(value: "\($0)")}
}
struct Card: Identifiable {
let id = UUID()
let value: String
var selected = false
}
extension Card: Equatable {
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.id == rhs.id
}
}
final class TestVM: ObservableObject {
@Published var deck = Deck()
}
struct CardView: View {
@Binding var card: Card // <-- here
var body: some View {
Text("\(card.value)")
.frame(width: 40, height: 80)
.background {Rectangle().stroke(card.selected ? .red : .black, lineWidth: 2)}
.background(card.selected ? .red.opacity(0.2) : .gray.opacity(0.2))
.onTapGesture {
card.selected.toggle() // <-- here
}
}
}
struct ContentView: View {
@StateObject var testVM = TestVM()
var body: some View {
HStack {
ForEach($testVM.deck.cards) { $card in // <-- here
if testVM.deck.cards.prefix(4).contains(card) {
CardView(card: $card) // <-- here
}
}
}
}
}