VStack 中的 SwiftUI 布局,其中一个 child 提供了 maxHeight 但可以使用小于该高度

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

我正在尝试在包含两个 children 的 VStack 中构建布局。第一个 child 应该占用第二个 child 未使用的所有可用 space。第二个 child 有一个基于其自身内容的首选大小。我想将第二个 child 的高度限制为最大高度,但它应该能够小于最大值(当它自己的内容不能使用所有高度时)。这应该都响应根视图大小,即 VStack 的 parent(因为设备可以旋转)。

我的尝试使用了 .frame(maxHeight: n) 修饰符,它似乎无条件地占据了整个 n 点的高度,即使被修改的视图没有使用它。这导致在 VStack 的第二个 child 上方和下方呈现白色 space。这个问题显示在下面的纵向预览中 - hasIdealSizeView 只有 57.6pts 的高度,但是包裹该视图的框架有 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))
        }
    }
}

观察到的结果与文档和在线博客文章中描述 .frame(maxHeight: n) 修饰符的方式一致(flow chart here 非常有帮助)。尽管如此,我似乎找不到另一种方法来构建这种类型的布局。

相关问题.frame(maxHeight: n) 的预期用例是什么?通过无条件地将视图包装在高度至少为 n 点的框架中,这似乎与我的预期相反。它似乎与 .frame(height: n) 没有什么不同,对提供的高度使用明确的值。

可能有很短的方法可以解决这个问题,但与此同时,这就是我所做的。 首先,我为您的 hasIdealSizeView 创建了一个结构,并将其设为 return 一个 GeometryProxy,这样我就可以 return HStack 的高度,在这种情况下,与您在文本上打印的高度相同看法。然后我使用 return 代理检查高度是否大于最大值,如果是,我将其设置为最大值,否则,将高度设置为零,这基本上允许原生 SwiftUI 灵活身高:

//
//  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)
                    }
            }
        }
    }
    
    
}

本例中 .minHeight 的行为很奇怪,远非直观。但我找到了一个使用稍微不同路线的解决方案:

这定义了展开视图的 minHeight(以在纵向模式下获得所需的布局),但向第二个添加了 .layoutPriority,使其首先定义自己,然后将剩余的 space 提供给上视图。

    var body: some View {
        GeometryReader { geometry in
            VStack(spacing: 0) {
                fullyExpandingView
                    .frame(minHeight: geometry.size.height / 4 * 3)
                hasIdealSizeView
                    .layoutPriority(1)
            }
        }
    }