0

I'm trying to implement a VStack grid that needs to have a HStack when a condition is met on the next item in a ForEach loop. I have the code below that displays the items in an array but I don't know how to get the next item to check against.

Here's what I have so far.

VStack {
            ForEach(item.cards, id: \.self) { item in
                switch item.card.size {
                case .Large:
                    LargeCard(card: item.card, viewModel: CardViewModel())
                case .Medium:
                    MediumCard(card: item.card, viewModel: CardViewModel())
                case .Small:
                    HStack(spacing: 16) {
                        SmallCard(card: item.card, viewModel: CardViewModel())
                        // This is where I think I need to check and I tried somthing like below, but the compiler crashes
                        if $0 + 1 < item.cards.count {
                            if item.card.size == .Small {
                                SmallCard(card: item.card, viewModel: CardViewModel())
                            }
                        }
                    }
                case .none:
                    Text("No more.")
                }
            }
        }

Here's the item struct:

struct Item: Decodable, Hashable {
    let card: Card
}

Here's what I'm wanting to get.

enter image description here

Warve
  • 481
  • 1
  • 8
  • 22
  • You can use `.enumerated` to have and item and index, so use index as you wish (eg. to access next item from container). Next can be helpful https://stackoverflow.com/a/59863409/12299030. – Asperi Feb 05 '22 at 07:45
  • Does this answer your question? [Get index in ForEach in SwiftUI](https://stackoverflow.com/questions/57244713/get-index-in-foreach-in-swiftui) – vadian Feb 05 '22 at 07:56
  • I've tried both approaches which result in the app failing to build without any build errors or the compiler just hangs. The app builds if I comment out the if statement inside the HStack. You can perform if blocks inside a HStack that's inside a forEach?? – Warve Feb 05 '22 at 08:34
  • could you show the code for item. Does it have an `id` property? – workingdog support Ukraine Feb 05 '22 at 10:01
  • It doesn't have an ID but its a simple struct. Added to above – Warve Feb 05 '22 at 10:52

1 Answers1

1

you could try adding a id to your struct Item, such as:

struct Item: Identifiable, Decodable, Hashable {
    let id = UUID()
    let card: Card
}

and then use:

VStack {
    ForEach(item.cards, id: \.self) { theItem in
        switch theItem.card.size {
        case .Large:
            LargeCard(card: theItem.card, viewModel: CardViewModel())
        case .Medium:
            MediumCard(card: theItem.card, viewModel: CardViewModel())
        case .Small:
            HStack(spacing: 16) {
                SmallCard(card: theItem.card, viewModel: CardViewModel())
                // here use the id to find the next item
                if let ndx = item.cards.firstIndex(where: {$0.id == theItem.id}) {
                    if ndx + 1 < item.cards.count {
                        let nextItem = item.cards[ndx + 1]
                        if nextItem.card.size == .Small {
                            SmallCard(card: nextItem.card, viewModel: CardViewModel())
                        }
                    }
                }
            }
        case .none:
            Text("No more.")
        }
    }
}

You could also use enumerated as mentioned in the comments, such as:

VStack {
    ForEach(Array(item.cards.enumerated()), id: \.offset) { index, theItem in
        switch theItem.card.size {
        case .Large:
            LargeCard(card: theItem.card, viewModel: CardViewModel())
        case .Medium:
            MediumCard(card: theItem.card, viewModel: CardViewModel())
        case .Small:
            HStack(spacing: 16) {
                SmallCard(card: theItem.card, viewModel: CardViewModel())
                // here use the index to find the next item
                if index + 1 < item.cards.count {
                    let nextItem = item.cards[index + 1]
                    if nextItem.card.size == .Small {
                        SmallCard(card: nextItem.card, viewModel: CardViewModel())
                    }
                }
            }
        case .none:
            Text("No more.")
        }
    }
}

Note, it looks like you should be using .environmentObject(viewModel) to pass a single CardViewModel() to the views, instead of creating a new CardViewModel() each time.

  • Can you confirm that this runs within your Xcode as I get `The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions`. I've seen that SwiftUI has memory leak issues in Xcode 13.2.*, but this is simple logic that should compile. – Warve Feb 05 '22 at 11:42
  • well, I can't compile or run the code, since it is not complete. There are too many missing parts. If you show all the required code then I can try it. Start by showing the code for the View, including the declaration of `item`. Then we can progress from that. – workingdog support Ukraine Feb 05 '22 at 11:56
  • Marked as the answer as I ran it on a previous Xcode and it worked. Thanks for your help and suggestions – Warve Feb 05 '22 at 12:38