Search code examples
swiftuiswiftui-foreach

Same ForEach loop twice in one SwiftUI View


When I use a ForEach loop over an array twice within a view, I get the following warning at runtime:
LazyVGridLayout: the ID 84308994-9D16-48D2-975E-DC40C5F9EFFF is used by multiple child views, this will give undefined results!

The reason for this is clear so far, but what is the smartest way to work around this problem?

The following sample code illustrates the problem:

import SwiftUI

// MARK: - Model

class Data: ObservableObject
{
    @Published var items: [Item] = [Item(), Item(), Item()]
}

struct Item: Identifiable
{
    let id = UUID()
    var name: String = ""
    var description: String = ""
}

// MARK: - View

struct MainView: View {
    @StateObject var data: Data

    private var gridItems: [GridItem] { Array(repeating: GridItem(), count: data.items.count) }
    
    var body: some View {
        LazyVGrid(columns: gridItems, alignment: .leading, spacing: 2) {
            ForEach(data.items) { item in
                Text(item.name)
            }
            ForEach(data.items) { item in
                Text(item.description)
            }
        }
    }
}


// MARK: - App

@main
struct SwiftUI_TestApp: App {
    
    var body: some Scene {
        WindowGroup {
            MainView(data: Data())
        }
    }
}

I could possibly divide the view into several SubViews.
Are there any other options?

Edit:
This is the body of the real app:

    var body: some View {
        VStack {
            LazyVGrid(columns: columns, alignment: .leading, spacing: 2) {
                Text("")
                ForEach($runde.players) { $player in
                    PlayerHeader(player: $player)
                }
                
                ForEach(Score.Index.allCases) { index in
                    Text(index.localizedName)
                    ForEach(runde.players) { player in
                        Cell(player: player, active: player == runde.activePlayer, index: index)
                    }
                }
                
                Text ("")
                ForEach(runde.players) { player in
                    PlaceView(player: player)
                }
            }
            .padding()
        }
    }

Solution

  • If you really need that kind of grid filling, then it is possible just to use different identifiers for those ForEach containers, like

        LazyVGrid(columns: gridItems, alignment: .leading, spacing: 2) {
            ForEach(data.items) { item in
                Text(item.name).id("\(item.id)-1")      // << here !!
            }
            ForEach(data.items) { item in
                Text(item.description).id("\(item.id)-2")    // << here !!
            }
        }
    

    Tested with Xcode 13beta / iOS 15