2

i'm writing a SwiftUI app and would like a container view which "pushes" its children into a row and, once the row fills up, starts wrapping subsequent children to the subsequent row—in other words, a container which emulates the behavior of CSS flexbox's flex-wrap: wrap attribute. the children have variable, dynamic widths, but are the same height (although i suspect most non-insane solutions will be robust to variable heights). the number of children is also dynamic.

toplevel, my question is just: what's the simplest way to do this? if i need to dip into UIKit or obj-c land, so be it, but i'd prefer solutions which are standalone (i.e., not pod Yoga).

(NB: i am new to iOS dev, but have working familiarity with React and with all the ~newfangled CSS layouts.)

so far my best lead is the LazyVGrid View that ships with iOS 14. in particular, it seems to me like a single adaptive GridItem with no maximum (defaulting to infinity) should do the trick... but it does not, or at least i haven't figured out how yet. (the best reference slash only detailed reference for *Grid i've found is: https://swiftui-lab.com/impossible-grids/)

i initially tried this:

let columns = [
  GridItem(.adaptive(minimum: 42), alignment: .leading)
]

alongside

LazyVGrid(columns: columns, spacing: 10) {
  Text("Hello, world!")
    .fixedSize(horizontal: true, vertical: false)
    .border(Color.gray)
  Text("lol")
    .fixedSize(horizontal: true, vertical: false)
    .border(Color.gray)
  Text("bananananananananana")
    .fixedSize(horizontal: true, vertical: false)
    .border(Color.gray)
}

but that didn't work. setting an explicitly large maximum (50000) didn't help either. my understanding here was that the LazyVGrid should allow its children to expand their grid cells, but that's clearly not what's happening here.

i wanted to make sure this wasn't Text being all shy and meek and declining to express its size preference, so i tried being more explicit with a frame:

LazyVGrid(columns: columns, spacing: 10) {
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .frame(minWidth: 100, maxWidth: 100)
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
  Text("sup")
    .border(Color.gray)
}

but this also doesn't work. i've gotten the wrapping behavior, but not the "flex" behavior, which at least by my reading is what .adaptive purports to do on the tin.

so my secondary question is: what am i misunderstanding about the way that an adaptive LazyVGrid performs size proposal to its children and negotiates the size of its "adaptive columns"? is there a way to do what i want with grids, and if not, why not (and what's a better plan)?

mxawng
  • 31
  • 1
  • 3

2 Answers2

1

my understanding of .adaptive was incorrect—its columns are always evenly spaced; the maximum argument just allows those columns to expand (in lock-step) so that they collectively fill the parent's space. i'd thought that each column could individually expand to the max width from the min width.

a friend corrected me and linked me an HStack-based solution: SwiftUI HStack with Wrap

(i'm leaving this question up because the best Lazy*Grid writeup i found, linked above, can i think be interpreted both ways on this point, hence my error.)

mxawng
  • 31
  • 1
  • 3
0

Edit: realised I focused on the wrap not the flex part of your question and don't think this is what you're after.

I think you just need to set the alignment property and then you get the same effect as CSS flexbox's flex-wrap: wrap shown at CSS tricks..

This seems to work for me, only change to your code is setting alignment: .leading in the LazyVGrid initialiser. Result with alignment: .leading

struct ContentView: View {
    
    let columns = [
      GridItem(.adaptive(minimum: 42), alignment: .leading)
    ]
    
    var body: some View {
        LazyVGrid(columns: columns, alignment: .leading, spacing: 10) {
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
          Text("sup")
            .border(Color.gray)
        }
    }
}
jknlsn
  • 373
  • 1
  • 6