根据其元素动态调整 GeometryReader 高度 [英] Dynamically size a GeometryReader height based on it's elements

查看:31
本文介绍了根据其元素动态调整 GeometryReader 高度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试做一些我认为非常直接的事情.

I'm trying to do something that's pretty straight forward in my mind.

我想要一个 VStack 的子视图根据其内容动态改变其高度(下面示例中的 ProblematicView).

I want a subview of a VStack to dynamically change its height based on its content (ProblematicView in the sample below).

它通常工作得很好,但在这种情况下,ProblematicView 包含一个 GeometryReader(用于模拟多行的 HStack).

It usually works pretty well, but in this case ProblematicView contains a GeometryReader (to simulate a HStack over several lines).

然而,GeometryReader 贪婪地占用所有空间(如果您删除 GeometryReader 及其内容,则会发生预期的行为).不幸的是,在父视图(下面示例中的 UmbrellaView)上,UmbrellaView VStack 将自身的 50% 分配给 ProblematicView,而不是显示视图内容的最小尺寸.

However, the GeometryReader greedily takes all the space it can (the expected behavior happens if you remove the GeometryReader and it's content). Unfortunately on the Parent view (UmbrellaView in the sample below), the UmbrellaView VStack assigns 50% of itself to the ProblematicView instead of the minimal size to display the content of the view.

我花了几个小时来处理 min/ideal/maxHeight 框架参数,但无济于事.

I've spend a few hours playing with min/ideal/maxHeight frame arguments, to no avail.

我想要实现的目标可行吗?

Is what I'm trying to achieve doable?

我在底部添加了图片以在视觉上进行澄清.

I added pictures at the bottom to clarify visually.

struct UmbrellaView: View {
    var body: some View {
        VStack(spacing: 0) {
            ProblematicView()
            .background(Color.blue)

            ScrollView(.vertical) {
                Group {
                    Text("A little bit about this").font(.system(size: 20))
                    Divider()
                }
                Group {
                    Text("some").font(.system(size: 20))

                    Divider()
                }
                Group {
                    Text("group").font(.system(size: 20)).padding(.bottom)
                    Divider()
                }
                Group {
                    Text("content").font(.system(size: 20))
                }
            }

        }
    }
}


struct ProblematicView: View {

    var body: some View {
        let tags: [String] = ["content", "content 2 ", "content 3"]
        var width = CGFloat.zero
        var height = CGFloat.zero

        return VStack(alignment: .center) {
            Text("Some reasonnably long text that changes dynamically do can be any size").background(Color.red)
            GeometryReader { g in
                ZStack(alignment: .topLeading) {
                    ForEach(tags, id: \.self) { tag in
                        TagView(content: tag, color: .red, action: {})
                            .padding([.horizontal, .vertical], 4)
                            .alignmentGuide(.leading, computeValue: { d in
                                if (abs(width - d.width) > g.size.width)
                                {
                                    width = 0
                                    height -= d.height
                                }
                                let result = width
                                if tag == tags.last! {
                                    width = 0 //last item
                                } else {
                                    width -= d.width
                                }
                                return result
                            })
                            .alignmentGuide(.top, computeValue: {d in
                                let result = height
                                if tag == tags.last! {
                                    height = 0 // last item
                                }
                                return result
                            })
                    }
                }.background(Color.green)
            }.background(Color.blue)
        }.background(Color.gray)
    }
}

struct TagView: View {
    let content: String
    let color: Color
    let action: () -> Void?

    var body: some View {
        HStack {
            Text(content).padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
            Button(action: {}) {
                Image(systemName: "xmark.circle").foregroundColor(Color.gray)
            }.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7))
        }
        .background(color)
        .cornerRadius(8.0)
    }
}

struct ProblematicView_Previews: PreviewProvider {
    static var previews: some View {
        return ProblematicView()
    }
}


struct UmbrellaView_Previews: PreviewProvider {
    static var previews: some View {
        return UmbrellaView()
    }
}

推荐答案

由于 GeometryReader 本质上的鸡蛋"问题,主题问题的解决方案只能在运行时进行,因为1) 初始高度未知 2) 需要根据所有可用的外部尺寸计算内部尺寸 3) 需要将外部尺寸紧缩到计算出的内部尺寸.

Due to "hen-egg" problem in nature of GeometryReader the solution for topic question is possible only in run-time, because 1) initial height is unknown 2) it needs to calculate internal size based on all available external size 3) it needs to tight external size to calculated internal size.

所以这是可能的方法(在您的代码中有一些额外的修复)

So here is possible approach (with some additional fixes in your code)

1) 预览 2-3) 运行时

1) Preview 2-3) Run-time

代码:

struct ProblematicView: View {

    @State private var totalHeight = CGFloat(100) // no matter - just for static Preview !!
    @State private var tags: [String] = ["content", "content 2 ", "content 3", "content 4", "content 5"]

    var body: some View {
        var width = CGFloat.zero
        var height = CGFloat.zero

        return VStack {
            Text("Some reasonnably long text that changes dynamically do can be any size").background(Color.red)
            VStack { // << external container
                GeometryReader { g in
                    ZStack(alignment: .topLeading) { // internal container
                        ForEach(self.tags, id: \.self) { tag in
                            TagView(content: tag, color: .red, action: {
                                    // self.tags.removeLast()         // << just for testing
                                })
                                .padding([.horizontal, .vertical], 4)
                                .alignmentGuide(.leading, computeValue: { d in
                                    if (abs(width - d.width) > g.size.width)
                                    {
                                        width = 0
                                        height -= d.height
                                    }
                                    let result = width
                                    if tag == self.tags.last! {
                                        width = 0 //last item
                                    } else {
                                        width -= d.width
                                    }
                                    return result
                                })
                                .alignmentGuide(.top, computeValue: {d in
                                    let result = height
                                    if tag == self.tags.last! {
                                        height = 0 // last item
                                    }
                                    return result
                                })
                        }
                    }.background(Color.green)
                    .background(GeometryReader {gp -> Color in
                        DispatchQueue.main.async {
                            // update on next cycle with calculated height of ZStack !!!
                            self.totalHeight = gp.size.height
                        }
                        return Color.clear
                    })
                }.background(Color.blue)
            }.frame(height: totalHeight)
        }.background(Color.gray)
    }
}

struct TagView: View {
    let content: String
    let color: Color
    let action: (() -> Void)?

    var body: some View {
        HStack {
            Text(content).padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
            Button(action: action ?? {}) {
                Image(systemName: "xmark.circle").foregroundColor(Color.gray)
            }.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7))
        }
        .background(color)
        .cornerRadius(8.0)
    }
}

这篇关于根据其元素动态调整 GeometryReader 高度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆