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)
           }

            }
        }
ty1
  • 334
  • 4
  • 19
  • if you are iterating two cards at a time you should divide the count by 2. Something like `ForEach(0.. – Leo Dabus Mar 01 '20 at 00:12
  • Unfortunately it could be odd or even in this case – ty1 Mar 01 '20 at 00:20
  • What do you want to happen if there is only one card left? The method above will simply ignore it. – Leo Dabus Mar 01 '20 at 00:21
  • The last snippet in my question handles it. When the index + 1 is greater than the count of items in the array, it prints just one, instead of both - in which case would be the end of the array. – ty1 Mar 01 '20 at 00:26
  • Try something like this: https://stackoverflow.com/questions/58430112/swiftui-increment-variable-in-a-foreach-loop – zgluis Mar 01 '20 at 00:29
  • 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. – Leo Dabus Mar 01 '20 at 00:35
  • 1
    @LeoDabus Ah you're right, now that i think about it the extra block looks quite silly.. – ty1 Mar 01 '20 at 00:44
  • @TechyTy Regarding `The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions` SwiftUI it is at its very beginning stage. It is really annoying to not be able to add such basic conditions to your code. – Leo Dabus Mar 01 '20 at 00:55
  • 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! – ty1 Mar 01 '20 at 00:58
  • This code does too much. It fiddles with indices, makes deterinations about even/odd, etc. I would recommend you make a separate method that takes your array of cards and returns you them in an array of tuples. Like this: https://stackoverflow.com/questions/40841663/swift-whats-the-best-way-to-pair-up-elements-of-an-array – Alexander Mar 01 '20 at 01:44
  • @Alexander-ReinstateMonica the main issue is a condition to show or not the second Card view. Regarding grouping every two elements https://stackoverflow.com/a/34454633/2303865 or https://stackoverflow.com/a/54524110/2303865 – Leo Dabus Mar 01 '20 at 02:36

2 Answers2

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

zgluis
  • 3,021
  • 2
  • 17
  • 22
  • 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 – Leo Dabus Mar 01 '20 at 17:40
  • 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: https://gist.github.com/zgluis/c334f2631c728185154d384b8c9d669d @LeoDabus – zgluis Mar 01 '20 at 18:46
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)
            }
ty1
  • 334
  • 4
  • 19