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

5
  • 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 stackoverflow.com/a/59863409/12299030. Commented Feb 5, 2022 at 7:45
  • Does this answer your question? Get index in ForEach in SwiftUI Commented Feb 5, 2022 at 7: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?? Commented Feb 5, 2022 at 8:34
  • could you show the code for item. Does it have an id property? Commented Feb 5, 2022 at 10:01
  • It doesn't have an ID but its a simple struct. Added to above Commented Feb 5, 2022 at 10:52

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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.
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.
Marked as the answer as I ran it on a previous Xcode and it worked. Thanks for your help and suggestions

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.