'How to "unpack" the content of a ForEach
I am building a @resultBuilder in my app. This resultBuilder takes Views and returns them in an array. This is necessary because the view which takes the content (WrappingHStack) needs it as an array. My current source looks like this:
@resultBuilder public struct ViewArrayBuilder {
public static func buildBlock() -> [EmptyView] {
[EmptyView()]
}
public static func buildBlock<Content: View>(_ content: Content) -> [AnyView] {
[AnyView(content)]
}
}
struct WrappingHStack: View {
@usableFromInline var alignment: Alignment = .topLeading
@usableFromInline var spacing: CGFloat = 8.0
@usableFromInline var maxRows: Int = Int.max
@usableFromInline let content: [AnyView]
@State private var height: CGFloat = 0
@usableFromInline init(alignment: Alignment = .topLeading,
spacing: CGFloat = 8.0,
maxRows: Int = Int.max,
content: [AnyView]) {
self.alignment = alignment
self.spacing = spacing
self.maxRows = maxRows
self.content = content
}
@inlinable public init(alignment: Alignment = .topLeading,
spacing: CGFloat = 8.0,
maxRows: Int = Int.max,
@ViewArrayBuilder content: () -> [AnyView]) {
self.init(alignment: alignment, spacing: spacing, maxRows: maxRows, content: content())
}
}
This all works fine if used like so:
WrappingHStack(maxRows: 2) {
Text("1")
Text("2")
...
}
If used with a ForEach in the closure it is recognised as one view and an array with the single ForEach is returned. But I want to get the contents of the ForEach and put them in the array. I was thinking about checking the type of the content and if a ForEach is detected it would be "unpacked".
public static func buildBlock<Content: View>(_ content: Content) -> [AnyView] {
// Doesn’t work because of this error message:
// Protocol 'RandomAccessCollection' as a type cannot conform to the protocol itself
if let forEachContent = content as? ForEach<RandomAccessCollection, Any, Any> {
return content.data.map({ elem in AnyView(content.content(elem)) })
}
return [AnyView(content)]
}
But I can’t seem to find a way to correctly ask for the ForEach type.
How would this be done? Or are there better ways to "unpack" the content of the ForEach?
Update
Why I need that?
I try to create a "wrapping" HStack. That is a view that lays out its children horizontally like a normal HStack. Once the available width is used up it wraps the children and continues on the next line. I base my approach on this article.
So at one point in time I do need the views created with the ForEach construct to lay them out as I want to. If I were able to correctly cast the content parameter of the buildBlock method to the ForEach type I could use the content function of ForEach to create the views. Like shown in the code block above.
I’m also open for other suggestions which accomplish the wrapping stack I need. (The lazy grids Apple provides are not what I want. My child views are of different width and I want them to flow like text would within the WrappingHStack.)
Solution 1:[1]
WrappingHStack library does the wrapping and can be used as a forEach:
WrappingHStack(1...30, id:\.self) {
Text("Item: \($0)")
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Viktor Sec |

