【问题标题】:Dynamically size a GeometryReader height based on it's elements根据其元素动态调整 GeometryReader 高度
【发布时间】:2020-08-02 06:42:24
【问题描述】:

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

我希望 VStack 的子视图根据其内容动态更改其高度(下例中的 ProblematicView)。

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

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

我已经花了几个小时使用 min/ideal/maxHeight 框架参数,但无济于事。

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

我在底部添加了图片以使视觉清晰。

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

【问题讨论】:

    标签: swiftui


    【解决方案1】:

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

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

    1) 预览 2-3) 运行时

    代码:

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

    【讨论】:

    • 这非常有效。我希望有一种方法可以不在运行时处理这个问题,但它仍然非常好。
    猜你喜欢
    • 2012-02-27
    • 2020-11-23
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    • 2012-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多