19

I'm using ForEach to create a loop. However, I want to render a Rectangle conditionally depending upon the count of the loop. For example, don't render the rectangle if the last loop iteration.

What's the correct syntax for this?

I'm looking for something like this (non working pseudo-code)

ForEach(arrayOfThings, id: \.title) { thing in
    // Stuff that happens on every iteration of loop

    // Conditional section
    if (thing.endIndex-1) {
        Rectangle()
        .frame(width: 100, height: 1, alignment: .bottom)
        .foregroundColor(Color("666666"))
    }
}

I am aware there are things like for (offset, element) in array.enumerated() { } but you can't use them in views. I wondered if there is a convenience feature in ForEach to solve this need?

Currently I am doing this to workaround:

ForEach(0..<arrayOfThings.count) { i in
    // Stuff that happens on every iteration of loop

    // Conditional section
    if (i = self.arrayOfThings.count-1) {
        Rectangle()
        .frame(width: 100, height: 1, alignment: .bottom)
        .foregroundColor(Color("666666"))
    }
}
1
  • 1
    I think the index solution that you are currently doing is the correct solution, not a workaround. The parameter in the ForEach callback (thing or i in your two examples) is just the type of the elements of the array you pass to the ForEach (arrayOfThings), so they have no knowledge of their placement within the array unless you code that into their classes, which seems a lot more convoluted and much more of a workaround than the index solution you are already using. Commented Oct 15, 2019 at 15:50

2 Answers 2

39

If your arrayOfThings is Equatable you could do

ForEach(arrayOfThings, id: \.title) { thing in
    if thing == arrayOfThings.last {
        // do something
    }
}

Which is more readable than checking index vs count in my opinion.

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

5 Comments

Not sure if it is merely my ineptitude (probable) but this doesn't seem to work. My struct wasn't Equatable by default so added my own func inside the struct and added the Equatable keyword to the struct. However, .last doesn't seem to work. It just toggles all the rectangles off in my scenario
This also would not work if the last element had a duplicate in the middle of the array (e.g [1,2,3,4,5,3] - the third and last elements in the array would cause the if to execute, instead of just the last one. You would have to make sure each element is truly unique for this to be reliable.
It sounds like the == is true for all items? Which could indicate that your Equatable implementation could be at fault? Note: make sure you have an else to the check because swiftui will reuse cells.
This answer is not correct. The problem is that arrayOfThings does not have to be unique, only the title property must be. E.g. using an array of struct Thing {var a=0;var b=0;static func == (lhs: Self, rhs: Self) -> Bool { lhs.b == rhs.b}}, ForEach([Thing(a: 1, b: 1), Thing(a: 2, b: 1)], id:\.a) { thing in will not work correctly and enter the if condition on every element
@Daniel The answer assumes that the property used as view id is unique and included in the equatable check which is very reasonable.
6

To know if it is the last element, you can just test if the KeyPath used as id is the same for last element and the current element.

Note that the id parameter is ensured to be unique (at runtime) and Equatable. Therefore, you cannot have any problems regarding duplicated elements or missing equality, like you could by using the element directly even if it is not used as id.

ForEach(myArray, id: \.someProperty) { element in
    if element.someProperty == myArray.last?.someProperty {
        // do something with the last element
    }
}

or in case you use an Identifiable element directly or use id: \.self:

ForEach(myArray) { element in
    if element == myArray.last {
        // do something with the last element
    }
}

5 Comments

You mean Equatable for the second example, right?
No, I mean if you use an Identifiable object at ForEach(myArray)
You can't do this check if element.someProperty == myArray.last?.someProperty inside a ForEach. It's expecting a view builder
@lopes710 Conditional Views are allowed in a @ViewBuilder, e.g. check this article
@Daniel I misinterpret the code. I was assuming in the if to have other code e.g. a print instead of a view builder. In that case it works yes.

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.