I have a problem with a @State
not updating its value and I do not understand what is causing this issue. If you can explain the reason of the following code and behaviour I would greatly appreciate it.
I have th following UI class:
struct CardGrid: View {
private static let portraitViewColumns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)
var cards: [Card]
@State private var selectedIndex: Int = 0
@State private var showDetails = false
var body: some View {
LazyVGrid(columns: CardGrid.portraitViewColumns) {
ForEach(cards) { card in
Button {
// the line of code in question
selectedIndex = cards.index(matching: card)!
showDetails = true
} label: {
UICard(card: card)
}
}
}
.sheet(isPresented: $showDetails) {
CardDetailView(cards: cards, index: selectedIndex)
}
}
}
and an extension which is called in the marked line above:
extension Collection where Element: Identifiable {
func index(matching element: Element) -> Self.Index? {
firstIndex(where: { $0.id == element.id })
}
}
That CardGrid
has an array of Card
objects. For each object a button is created in a VGrid
. If a button is clicked a detail view opens as sheet, displaying details about the card associated with the button. Because the detail view allows paging between Card
objects of the array of cards, it is called with the array of cards itself and an index of the card to initialise the view with. This is done in the commented line by finding the index of the card that was tapped/clicked on within the array of cards.
Expected Behaviour
Given an array of cards with [Card 1, Card 2, Card 3]
it is expected that if the user taps on the button for Card 2
the func index(matching element: Element) -> Self.Index?
method returns the index 1 and the card detail view opens up displaying details for Card 1
.
Observed Behaviour
Method func index(matching element: Element) -> Self.Index?
returns index 1 but the state selectedIndex
does not update to 1 but remains at 0. The card detail view shows up, displaying details about Card 1
. The same is true if the user taps on the button for Card 3
.
This behaviour will persist until the user taps on the button for Card 1
, displaying the card details for Card 1
. Closing the detail view sheet and the tap on the button for Card 2
will now set the selectedIndex
to 1 and open the detail view for Card 2
correctly as was expected.
I do not understand what causes the state not to update and why I starts updating once the user invokes the card detail view for the first element of the cards
array.
CodePudding user response:
Unfortunately I cannot mark jnpdx's comment as answer because that is exactly what caused the problem.
I was able to resolve the issue by refactoring the code to this:
struct CardGrid: View {
private static let portraitViewColumns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)
var cards: [Card]
// stored the selected card in a state
@State private var selectedCard: Card? = nil
var body: some View {
LazyVGrid(columns: CardGrid.portraitViewColumns) {
ForEach(cards) { card in
Button {
selectedCard = card
} label: {
UICard(card: card)
}
}
}
// use the sheet(item) method instead and calculate the index in it
.sheet(item: $selectedCard) { c in
CardDetailView(cards: cards, index: cards.index(matching: c)!)
}
}
}
``
CodePudding user response:
Did you try using just a regular for loop with an index variable to iterate instead of a foreach? That way you don't need an extension and it will be easier to track and store the selected index.