I am using two ForEach()
in a nested way, one to display the sections, and the other to display the individual cells of the List view.
My problem is that the iterator variable from the first ForEach
is behaving in a very weird way when accessing it in the second ForEach
.
In this gif I attached, the first variable behind Round (ri
in the code snippet below) changes its value sometimes when I scroll up or down. However the value of this variable is always the same inside the first ForEach
, as we can see by the section name where the displayed round is always the correct one.
Here is the code with the nested ForEach
statements. However as it is presented here, you'll probably not manage to reproduce the bug. While maybe superstitiously, it seems that the more complex my View is, the more likely the bug is to appear.
List {
ForEach(Array(self.game.rounds.indices), id: \.self) { ri in
Section(header: Text("Round \(ri): \(self.game.rounds[ri])")) {
ForEach(Array(self.game.players.indices), id: \.self) { pi in
HStack {
Text("\(self.game.players[pi])")
Text("Round \(ri), Player \(pi)")
}
}
}
}
}
Here is the complete view with the nested ForEach
that causes the problems for me:
https://github.com/charelF/carioca/blob/master/unote/GameView.swift
I'm fairly certain that what I am encountering is actually a bug in SwiftUI. However I'm not sure, and this behaviour may be expected behaviour or some kind of asynchronous loading of cells.
Either I'm doing something wrong, or it seems that the answer by @Asperi does not really solve the problem. Here's a quick and dirty workaround that I tried to avoid the id's being the same
List {
ForEach([10,11,12,13,14,15,16,17,18,19] /*Array(self.game.rounds.enumerated())*/, id: \.self) { ri in
Section(header: Text("Round \(ri-10): \(self.game.rounds[ri-10])")) {
ForEach(Array(self.game.players.indices), id: \.self) { pi in
HStack {
Text("ri \(ri), pi \(pi)")
}
}
}
}
The id's are actually never the same, however the rows/cells are still messed up, as seen on this screenshot...
Moreover I'm struggling to find a good way to iterate over these two arrays, given that I need the indices, so I can really iterate over the arrays themselves. The only solutions I can think of is either something ugly like the one above, using .enumerate()
(which I tried, and got similar error as above), or maybe transforming my arrays into dictionaries with unique keys...
I believe I understand the problem. However I have no idea how to solve it. I tried to create custom structs for both Round
and Player
instead of the Int
that I used previously. This means I can write my nested ForEach
as follows:
List {
ForEach(self.game.rounds, id: \.id) { round in
Section(header: Text("\(round.number): \(round.desc)")) {
ForEach(self.game.players, id: \.id) { player in
Text("\(player.name), \(round.number)")
}
}
}
}
This required rewriting the logic behind Game.swift
https://github.com/charelF/carioca/blob/master/unote/Game.swift
Unfortunately this has not solved the problem. The order is still messed up. Even though I believe I have understood the issue, I don't know how to solve it any other way.
I tried the suggested answer here (Nested ForEach (and List) in Views give unexpected results) (enclosing the view inside the outer ForEach
with a group), however the error still persists
I finally got it to work, unfortunately my solution is more of a hack and really not very clean. The solution is indeed as in the answer, all the cells need unique identifiers.
List {
ForEach(self.game.rounds) { round in
Group {
Section(header: Text("\(round.number): \(round.desc)")) {
ForEach(self.game.scoreBoard[round]!, id: \.self.id) { sbe in
Text("\(round.number) \(sbe.id)")
}
}
}
}
}
As seen in the screenshot below, the 4 cells below round 7 have different identifiers than the 4 cells below round 8, which, if I understand correctly, solves my problem.
Unfortunately the solution required some, imo, ugly workarounds in my game logic. https://github.com/charelF/carioca/blob/master/unote/Game.swift
It fails due to non-unique identifiers for rows. It is used index, but it is repeated in different sections, so List row reuse caching engine confused.
In the described scenario it should be selected something unique for items that are shown in rows, like (scratchy)
ForEach(self.game.rounds) { round in // make round identifiable
// ...
ForEach(self.game.players) { player in // make player identifiable
and rounds
ids should not overlap with player
ids as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With