Home > Blockchain >  SwiftUI layout in VStack where one child is offered a maxHeight but can use less than that height
SwiftUI layout in VStack where one child is offered a maxHeight but can use less than that height

Time:04-13

I'm trying to build a layout inside a VStack that contains two children. The first child should take up all available space unused by the second child. The second child has a preferred size based on its own contents. I'd like to limit the height of the second child to a maximum height, but it should be able to take less than the maximum (when its own contents cannot make use of all the height). This should all be responsive to the root view size, which is the parent of the VStack (because the device can rotate).

My attempt uses the .frame(maxHeight: n) modifier, which seems to unconditionally takes up the entire n points of height, even when the view being modified doesn't use it. This results in whitespace rendered above and below the VStack's second child. This problem is shown in the Portrait preview below - the hasIdealSizeView only has a height of 57.6pts, but the frame that wraps that view has a height of 75pts.

import SwiftUI

struct StackWithOneLimitedHeightChild: View {
    var body: some View {
        GeometryReader { geometry in
            VStack(spacing: 0) {
                fullyExpandingView
                hasIdealSizeView
                    .frame(maxHeight: geometry.size.height / 4)
            }
        }
    }
    
    var fullyExpandingView: some View {
        Rectangle()
            .fill(Color.blue)
    }
    
    var hasIdealSizeView: some View {
        HStack {
            Rectangle()
                .aspectRatio(5/3, contentMode: .fit)
            Rectangle()
                .aspectRatio(5/3, contentMode: .fit)
        }
            // the following modifier just prints out the resulting height of this view in the layout
            .overlay(alignment: .center) {
                GeometryReader { geometry in
                    Text("Height: \(geometry.size.height)")
                        .font(.system(size: 12.0))
                        .foregroundColor(.red)
                }
            }
    }
}

struct StackWithOneLimitedHeightChild_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            StackWithOneLimitedHeightChild()
                .previewDisplayName("Portrait")
                .previewLayout(PreviewLayout.fixed(width: 200, height: 300))
            StackWithOneLimitedHeightChild()
                .previewDisplayName("Landscape")
                .previewLayout(PreviewLayout.fixed(width: 300, height: 180))
        }
    }
}

Xcode Preview

This observed result is consistent with how the .frame(maxHeight: n) modifier is described in the docs and online blog posts (the enter image description here

CodePudding user response:

There's probably a really short way to go about this but in the meantime here is what I did. Firstly I created a struct for your hasIdealSizeView and I made it return a GeometryProxy, and with that i could return the height of the HStack, in this case, the same height you were printing on to the Text View. then with that I used the return proxy to check if the height is greater than the maximum, and if it is, i set it to the maximum, otherwise, set the height to nil, which basically allows the native SwiftUI flexible height:

//
//  ContentView.swift
//  Test
//
//  Created by Denzel Anderson on 3/16/22.
//

import SwiftUI

struct StackWithOneLimitedHeightChild: View {
    
    @State var viewHeight: CGFloat = 0
    
    var body: some View {
        GeometryReader { geometry in
            VStack(spacing: 0) {
                fullyExpandingView
                    .overlay(Text("\(viewHeight)"))
                //                GeometryReader { geo in
                hasIdealSizeView { proxy in
                    viewHeight = proxy.size.height
                }
                .frame(height: viewHeight > geometry.size.height / 4 ? geometry.size.height / 4:nil)
            }
            .background(Color.green)
        }
    }
    
    
    var fullyExpandingView: some View {
        Rectangle()
            .fill(Color.blue)
    }
    
}
struct hasIdealSizeView:  View {
    
    var height: (GeometryProxy)->()
    
    var body: some View {
        HStack {
            Rectangle()
                .fill(.white)
                .aspectRatio(5/3, contentMode: .fit)
            Rectangle()
                .fill(.white)
                .aspectRatio(5/3, contentMode: .fit)
        }
        
        // the following modifier just prints out the resulting height of this view in the layout
        .overlay(alignment: .center) {
            GeometryReader { geometry in
                Text("Height: \(geometry.size.height)")
                    .font(.system(size: 12.0))
                    .foregroundColor(.red)
                    .onAppear {
                        height(geometry)
                    }
            }
        }
    }
    
    
}

  • Related