Swiftui Nested Foreach Causes Unexpected Ordering

SwiftUI Nested ForEach causes unexpected ordering

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.

SwiftUI Nested ForEach causes Fatal error: each layout item may only occur once

Every item in ForEach needs a unique ID. The code can be modified by adding the following line to the Text view in the inner ForEach to assign a unique ID:

.id("body\(roomPickupData.id)-\(roomData.key)")

ForEach(self.checkfrontVM.roomPickupDataArray) { roomPickupData in
Group {
Text(roomPickupData.sku)
.frame(width: 80, alignment: .leading)
.background(roomTypeColor[roomPickupData.sku])
.border(Color.black)
ForEach(roomPickupData.roomsForDate.sorted(by: { $0.key < $1.key}), id: \.key) { roomData in
Text(roomData.value.description)
.id("body\(roomPickupData.id)-\(roomData.key)") //<-
}
}
}

Swiftui listRowInsets makes unexpected changes when adding to an empty array

It does seem to be related to the if/else statement. When the List is dropped from the view hierarchy and then added back in, the insets seem to get messed up.

The inset amount looked familiar to me -- like the amount it should be inset if there were a delete button on the leading side. So, on a hunch I added an explicit call to set the edit mode to .inactive. That seems to work.

At the top of your view, add this:

@Environment(\.editMode) private var editMode

Then, in your deleteScorecard(scorecard:Scorecard):

func deleteScorecard(scorecard:Scorecard) {
scorecards = scorecards.filter{$0.id != scorecard.id}
editMode?.wrappedValue = .inactive //<-- here
}

This seems to explicitly turn off the editing mode and then avoids the bug when the List is added back into the view hierarchy.


Update -- secondary solution.

Here's another solution I came up with, since my code works with your sample code, but not with your app. In this version, the List is included in the view hierarchy the whole time, but the frame is set to a height of 0 if there aren't any items. There may be side effects because of your background color that you're setting, which is one of the reasons I didn't include this initially, but since the original solution wasn't ideal, now it's worth including:

var body: some View {
VStack {

Button {
scorecards = [
Scorecard(date: Date()),

]

} label: {
Text("Add Scorecard")
}


if scorecards.count < 1 {

VStack {
Spacer()

HStack{
Spacer()
Text("No previous scorecards. Start a scorecard!")
.font(.headline)
.padding(20)
.multilineTextAlignment(.center)
Spacer()
}
Spacer()
}

}

List {
ForEach(sortedScorecards, id:\.self) { scorecard in
if #available(iOS 15.0, *) {

Button {

} label: {
Card(scorecard: scorecard)

}.buttonStyle(.plain)
.listRowInsets(.init(top:7.5,
leading:0,
bottom:7.5,
trailing:0))
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button("Delete Scorecard", role: .destructive) {
deleteScorecard(scorecard: scorecard)
}
}


} else {
// Fallback on earlier versions
}
}
}
.frame(maxHeight: scorecards.count == 0 ? 0 : .infinity)
.onAppear {
UITableView.appearance().backgroundColor = .black
}
}

}

Can you specify a Where in a SwiftUI ForEach statement?

you could try something like this without the ForEach:

if let item = ItemList.first(where: {$0.id == "xxxx"}) {
Text(item.description)
}


Related Topics



Leave a reply



Submit