18

Edit This is a regression in iOS 15 beta. The code works as expected on iOS 14.5:

enter image description here

I have submitted a bug to Apple.


I have a dashboard-style screen in my SwiftUI app, where I am using a LazyVGrid with a single .adaptative column to layout my dashboard widgets, where widgets are laid out in wrapping rows.

It works as I want it to.

However, if a widget happens to be taller than others, I would like other widgets in the same row to grow vertically, so they end up having the same height as the tallest of the row.

This small bit of code illustrates my problem:

struct ContentView: View {
  var body: some View {
    LazyVGrid(columns: [.init(.adaptive(minimum: 45, maximum: 50), alignment: .top)]) {

      VStack {
        Spacer()
        Text("Hello")
      }
      .border(.red)

      Text("Lorem ipsum")
        .border(.blue)
    }
    .border(.green)
    .padding(.horizontal, 100)
  }
}

The result is:

screenshot

I would like the red box (VStack containing Spacer + Hello) to be as tall as the blue box (lorem ipsum).

How could I accomplish that?

Please don't suggest using an HStack, as the above example is only to illustrate my problem with LazyVGrid. I do need to use the grid because I have quite a few children to layout, and the grid works great between phone and iPad form factors (adjusting the number of columns dynamically, exactly as I want it).

Pascal Bourque
  • 5,101
  • 2
  • 28
  • 45
  • 1
    Does this answer your question? [How to make SwiftUI Text multilineTextAlignment start from Top and Center](https://stackoverflow.com/questions/66361758/how-to-make-swiftui-text-multilinetextalignment-start-from-top-and-center) – aheze Jul 19 '21 at 02:06
  • Btw [this is the result](https://raw.githubusercontent.com/aheze/DeveloperAssets/master/Screen%20Shot%202021-07-18%20at%207.08.26%20PM.png) that I get when running your code (iOS 14.5) – aheze Jul 19 '21 at 02:09
  • @aheze Interesting, I'm on iOS 15 Beta 3. I'll file a bug with Apple. – Pascal Bourque Jul 25 '21 at 13:34
  • @aheze Thanks for linking to the multiline text answer, but it does not answer my question unfortunately. In my example here, I used a `Text` because it was convenient, but in my real use-case the grid is laying out complex subviews. – Pascal Bourque Jul 25 '21 at 13:37
  • 2
    Did you ever find a solution for this? Having the same problem :/ – gsapienza Aug 29 '21 at 22:17
  • 1
    @gsapienza I filed a bug with Apple, but there hasn't been any reply/feedback from them. – Pascal Bourque Aug 30 '21 at 23:09
  • 1
    Have this problem too. – Todd Hoff Sep 21 '21 at 01:29
  • Was there ever a solution to this one? – DevB1 May 11 '22 at 09:30
  • It works as expected under 16.2. – nkalvi Mar 26 '23 at 16:11

3 Answers3

4

It looks like Apple begins (for unknown reason) to apply fixedSize for views in grid to make layout based on known intrinsic content sizes. The Spacer, Shape, Color, etc. do not have intrinsic size so we observe... that what's observed.

A possible approach to resolve this is perform calculations by ourselves (to find dynamically max height and apply it to all cells/views).

Here is a demo (with simple helper wrapper for cell). Tested with Xcode 13.2 / iOS 15.2

demo

struct ContentView: View {
    @State private var viewHeight = CGFloat.zero

    var body: some View {
        LazyVGrid(columns: [.init(.adaptive(minimum: 45, maximum: 50), alignment: .top)]) {

            GridCell(height: $viewHeight) {
                VStack {
                    Spacer()
                    Text("Hello")
                }
            }.border(.red)

            GridCell(height: $viewHeight) {
                Text("Lorem ipsum asdfd")
            }.border(.blue)
        }
        .onPreferenceChange(ViewHeightKey.self) {
            self.viewHeight = max($0, viewHeight)
        }
        .border(.green)
        .padding(.horizontal, 100)
    }
}

struct GridCell<Content: View>: View {
    @Binding var height: CGFloat
    @ViewBuilder let content: () -> Content

    var body: some View {
        content()
            .frame(minHeight: height)
            .background(GeometryReader {
                Color.clear.preference(key: ViewHeightKey.self,
                    value: $0.frame(in: .local).size.height)
            })
    }
}

struct ViewHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value += nextValue()
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Have not tried on XCode 13.2 with iOS 15.2. But by removing `value = nextValue()` in `ViewHeightKey`. The code works on XCode 13.3 with iOS 15.4. – HomeworkGT May 26 '22 at 02:31
  • Thank you! I actually had the opposite problem where my view was getting way over-expanded in iOS 14. Applying `fixedSize(horizontal: false, vertical: true)` to my item view fixed my issue. – walkingbrad May 27 '22 at 01:42
  • This did not work for me in Xcode 14 / iOS 15 until I changed the `reduce` closure to `let n = nextValue(); if n > 0 { value = n }` – Gereon Jul 07 '23 at 09:27
0

I had exactly same problem. My LazyVGrid looked great on iOS 14, but now its items have different heights.

I found a dirty workaround to force the items have same height: In my case I have only several items in each LazyVGrid (So it won't cause too much performance drop), and it is easy for me to know which item has the largest height. So I made a ZStack and put a transparent highest item behind the actual item.

struct ContentView: View {
  var body: some View {
    LazyVGrid(columns: [.init(.adaptive(minimum: 45, maximum: 50), alignment: .top)]) {
      ZStack {
        Text("Lorem ipsum")  // This is the highest item.
          .opacity(0)        // Make it transparent.
        Text("Hello")
      }
      .border(.red)

      Text("Lorem ipsum")
        .border(.blue)
    }
    .border(.green)
    .padding(.horizontal, 100)
  }
}

This workaround works in my case, but I don't recommend using it widely in your app especially when you have a lot of items in the grid.

CZJ
  • 13
  • 2
-1

I had the same issue. What worked for me was to set the minHeight of Spacer to 0.

here is some dummy example code

struct MasterView: View {
    let columns = [
        GridItem(.flexible(), spacing: 12),
        GridItem(.flexible())
    ]
    
    var body: some View {
        VStack {
            LazyVGrid(columns: columns, spacing: 12) {
                ForEach(infoTexts, id:\.self) { infoText in
                    InfoFieldView(text: infoText)
                }
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }
}


struct InfoFieldView: View {
    var text: String
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text("Some text")
                .padding(.bottom, 4)
                 
            // Autoresizing does not work if you dont set minLenght
            Spacer(minLength: 0)
        }
    }
}
Radun C
  • 69
  • 1
  • 3