0

I am getting an error related to accessing an item in an array using the provided Index in a ForEach loop with SwiftUI.

I have an array of information that is used to pass information to a struct to render a card. I need two of these cards per HStack, so I loop over the array and call the cards like so:

ForEach(0..<array.count){item in
  Card(name: array[item].name)
  Card(name: array[item+1].name)
}

But this throws the error: The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions

What I'm trying to accomplish is a bunch of Horizontal stacks, with 2 items each, in a single VStack. This way i have a list of 2 side by side cards. This seemed like a simple way to just brute force that behavior, but I keep running into the error.

I am probably just going to switch to a different style of Hstack that will wrap to next line for every 3rd added the row, but I'd still like to know if anyone can tell me why this error occurs. It appears to be such a simple operation, yet it can't be done by the compiler

Here is the actual code I'm running, if the sample above doesn't cut it. The strangest thing about this code is that it only fails after the SECOND item +1. I can run this if i only do it once in the code.

        ForEach(0..<self.decks.count){item in

             HStack(spacing: 30){
                if(item+1 < self.decks.count){
                    StudyCards(cardTitle: self.decks[item].deckTitle, cardAmt: self.decks[item].stackAmount, lastStdy: self.decks[item].lastStudied)

                    StudyCards(cardTitle: self.decks[item+1].deckTitle, cardAmt: self.decks[item+1].stackAmount, lastStdy: self.decks[item+1].lastStudied)
                }
                Spacer()
                    .padding(.bottom, 4)
             } else{
                    StudyCards(cardTitle: self.decks[item].deckTitle, cardAmt: self.decks[item].stackAmount, lastStdy: self.decks[item].lastStudied)
           }

            }
        }
11
  • if you are iterating two cards at a time you should divide the count by 2. Something like ForEach(0..<self.decks.count/2) { print($0*2) print($0*2+1) } Of course this assumes you have an even number of cards on your deck. Commented Mar 1, 2020 at 0:12
  • Unfortunately it could be odd or even in this case Commented Mar 1, 2020 at 0:20
  • 1
    Regarding dealing with the extra card, you can move your condition to before showing the second card. No need to have a whole separate block for it. Commented Mar 1, 2020 at 0:35
  • 1
    @LeoDabus Ah you're right, now that i think about it the extra block looks quite silly.. Commented Mar 1, 2020 at 0:44
  • 1
    @LeoDabus that was my worst fear haha. I guess I'll have to do some custom stuff and make a self-wrapping stack view. Oh well. Thanks for your other help! Commented Mar 1, 2020 at 0:58

2 Answers 2

0

I replicated your error, as you said, it only happens when you have more than one "item+1" inside ForEach, like this:

Text(self.array[item + 1] + " - " + self.array[item + 1])

So I think the problem is not related to your specific Views. One solution is to create a function that increments item and returns your view:

struct ContentView: View {
    @State var array: [String] = ["zero","one","two", "three"];

    var body: some View {
        VStack {
            ForEach(0..<array.count) { item in
                Text(self.array[item])
                if item + 1 < self.array.count {
                    self.makeView(item: item)
                }
            }

        }
    }

    func makeView(item: Int) -> Text {
        let nextItem = item + 1
        return Text(self.array[nextItem] + " - " + self.array[nextItem])
    }
}

live example

Reference: swiftui-increment-variable-in-a-foreach-loop

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

2 Comments

You missed part of the discussion. You need to iterate half of the collection 0..<(count/2+count%2), then index*2 for the first item and index*2+1 for the second
You should improve your question to make those conditions more clear. Anyway, the logic is the same, move calculations to functions, I made some quick fixes to add your conditions: gist.github.com/zgluis/c334f2631c728185154d384b8c9d669d @LeoDabus
0

Alright so I got a solution!

I used this implementation of a view builder here: (https://www.hackingwithswift.com/quick-start/swiftui/how-to-position-views-in-a-grid)

struct GridStack<Content: View>: View {
    let rows: Int
    let columns: Int
    let content: (Int, Int) -> Content

    var body: some View {
        VStack(spacing: 30){
            ForEach(0 ..< rows, id: \.self) { row in
                HStack(spacing: 30){
                    ForEach(0 ..< self.columns, id: \.self) { column in
                        self.content(row, column)
                    }
                }
            }
        }
    }

    init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
        self.rows = rows
        self.columns = columns
        self.content = content
    }
}

And then i call it like so below. I had to add the if statement because without it, it was rendering an extra card. Now i have successfully implemented a grid with 2 columns, and any amount of rows! No need for 3rd party libs!

        GridStack(rows: self.rows, columns: 2){ row, col in
            if(row*2 + col < self.count){
                StudyCards(cardTitle: self.decks[row*2 + col].deckTitle, cardAmt: self.decks[row*2 + col].stackAmount, lastStdy: self.decks[row*2 + col].lastStudied)
            }

Comments

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.