在 SwiftUI 中拥抱子视图

Posted

技术标签:

【中文标题】在 SwiftUI 中拥抱子视图【英文标题】:Hug subviews in SwiftUI 【发布时间】:2019-06-30 06:08:56 【问题描述】:

Dave Abrahams 在他关于自定义视图的 WWDC19 演讲中解释了 SwiftUI 布局的一些机制,但他遗漏了一些内容,我无法正确调整视图的大小。

有没有办法让 View 告诉它的容器它没有提出任何空间需求,但它会使用它给定的所有空间?另一种说法是容器应该拥抱它的子视图。

具体例子,我想要 c:

如果你有一些 Texts 在 VStack 中,就像在 a) 中一样,VStack 将采用它的宽度到最宽的子视图。

如果您添加 Rectangle,尽管如 b) 中那样,它将尽可能地扩展,直到 VStack 填满 容器。

这表明Texts 和Rectangles 在布局方面属于不同的类别,Text 具有固定大小,Rectangle 是贪婪的。 但是,如果我正在制作自己的 View,如何将其传达给我的容器?

我真正想要达到的结果是c)。 VStack 在确定其大小时应忽略 Rectangle(或我的自定义视图),然后一旦完成,然后它应该告诉 Rectangle 或我的自定义视图,它可以拥有多少空间。

鉴于 SwiftUI 似乎是自下而上布局,也许这是不可能的,但似乎应该有some 的方式来实现这一点。

【问题讨论】:

我认为您可以通过获取所有文本视图宽度、计算最大宽度并使用它来设置矩形的框架宽度来使其工作。让我看看我能不能让它工作,我会发布答案。 是的,使用你的技巧应该是可能的。我什至会将文本视图作为内容放在 CustomView 中,然后让 CustomView 添加矩形。但似乎应该有一些修饰符或其他东西来告诉视图以这种方式运行! 【参考方案1】:

没有修饰符 (AFAIK) 可以完成此操作,所以这是我的方法。如果这是您要经常使用的东西,那么创建自己的修饰符可能是值得的。

还要注意,这里我使用的是标准首选项,但锚首选项更好。在这里解释是一个沉重的话题。我写了一篇文章,你可以在这里查看:https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

您可以使用下面的代码来完成您要查找的内容。

import SwiftUI

struct MyRectPreference: PreferenceKey 
    typealias Value = [CGRect]

    static var defaultValue: [CGRect] = []

    static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) 
        value.append(contentsOf: nextValue())
    


struct ContentView : View 
    @State private var widestText: CGFloat = 0

    var body: some View 
        VStack 
            Text("Hello").background(RectGetter())
            Text("Wonderful World!").background(RectGetter())
            Rectangle().fill(Color.blue).frame(width: widestText, height: 30)
            .onPreferenceChange(MyRectPreference.self, perform:  prefs in
                for p in prefs 
                    self.widestText = max(self.widestText, p.size.width)
                
            )
    


struct RectGetter: View 

    var body: some View 
        GeometryReader  geometry in
            Rectangle()
                .fill(Color.clear)
                .preference(key: MyRectPreference.self, value: [geometry.frame(in: .global)])
        
    

【讨论】:

非常感谢,这太棒了,而且确实有效! 很高兴它可以工作 ;-) 首选项对于通过视图层次结构树传达信息非常有用。不幸的是,文档几乎完全丢失了。 哦,不,它实际上不适用于我的用例!我有自己的自定义视图,而不是 textviews,而且似乎它们没有正确地向几何阅读器报告它们的大小,所以它认为它们根本没有大小!很奇怪,因为它们显示正常 您能否发布一个不起作用的自定义视图示例? 我的自定义视图很复杂,所以我先做一个简单的。奇怪的是,如果我有一堆 textviews 作为内容,框架会被正确报告,但如果我在 textviews 之外添加自定义视图,我的框架阅读器再次认为总大小为 0!【参考方案2】:

所以我实际上找到了一种方法来做到这一点。首先,我尝试将 Spacers 以各种配置围绕视图放置,以尝试将其推到一起,但这没有用。然后我意识到我也许可以使用 .background 修饰符,这确实有效。它似乎让拥有的视图先计算它的大小,然后把它作为它的框架,这正是我想要的。

这只是一个示例,其中包含一些获得正确高度的技巧,但这是一个小细节,在我的特定用例中不需要它。如果你足够聪明,可能也不在这里。

var body: some View 
    VStack(spacing: 10) 
        Text("Short").background(Color.green)
        Text("A longer text").background(Color.green)
        Text("Dummy").opacity(0)
    
    .background(backgroundView)
    .background(Color.red)
    .padding()
    .background(Color.blue)


var backgroundView: some View 
    VStack(spacing: 10) 
        Spacer()
        Spacer()
        Rectangle().fill(Color.yellow)
    

蓝色视图和所有彩色背景当然只是为了更容易看到。 这段代码产生了这个:

【讨论】:

以上是关于在 SwiftUI 中拥抱子视图的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中从减去的子视图中关闭视图

SwiftUI 将数据传递给子视图

如何在最新的 SwiftUI App 中获取窗口或视图的所有子视图?

如何在 SwiftUI 中重置子视图?

如何在 SwiftUI 的父视图中获取子视图的变量值?

如何在 SwiftUI 中禁用子视图上的特定状态更改动画