在 SwiftUI 中拥抱子视图
Hug subviews in SwiftUI
Dave Abrahams 在他关于自定义视图的 WWDC19 演讲中解释了 SwiftUI 布局的一些机制,但他遗漏了一些内容,我无法正确调整视图大小。
有没有办法让 View 告诉它的容器它没有提出任何 space 要求,但它会使用所有给定的 space?另一种说法是容器应该拥抱它的子视图。
具体的例子,我想要c:
如果你在 VStack
中有一些 Text
,比如 a),VStack
将采用它的宽度到最宽的子视图。
如果像 b) 那样添加 Rectangle
,它将尽可能地扩展,直到 VStack
填满 其 容器。
这说明Text
和Rectangle
在布局上属于不同的类别,Text
是固定大小的,Rectangle
是贪心的。 但是,如果我自己制作 View
,我该如何将其传达给我的容器?
我其实想要达到的结果是c)。 VStack 在确定其大小时应忽略 Rectangle(或我的自定义视图),然后一旦完成,然后 它应该告诉 Rectangle 或我的自定义视图,有多少 space 它可以有。
鉴于 SwiftUI 似乎是自下而上布局,也许这是不可能的,但似乎应该有 some
方式来实现。
没有修饰符 (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)])
}
}
}
所以我实际上找到了一种方法来做到这一点。首先,我尝试在各种配置中将 Spacer 放置在视图周围,试图将它们推到一起,但这没有用。然后我意识到我也许可以使用 .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)
}
}
蓝色视图和所有彩色背景当然只是为了更容易看到。
此代码产生此:
Dave Abrahams 在他关于自定义视图的 WWDC19 演讲中解释了 SwiftUI 布局的一些机制,但他遗漏了一些内容,我无法正确调整视图大小。
有没有办法让 View 告诉它的容器它没有提出任何 space 要求,但它会使用所有给定的 space?另一种说法是容器应该拥抱它的子视图。
具体的例子,我想要c:
如果你在 VStack
中有一些 Text
,比如 a),VStack
将采用它的宽度到最宽的子视图。
如果像 b) 那样添加 Rectangle
,它将尽可能地扩展,直到 VStack
填满 其 容器。
这说明Text
和Rectangle
在布局上属于不同的类别,Text
是固定大小的,Rectangle
是贪心的。 但是,如果我自己制作 View
,我该如何将其传达给我的容器?
我其实想要达到的结果是c)。 VStack 在确定其大小时应忽略 Rectangle(或我的自定义视图),然后一旦完成,然后 它应该告诉 Rectangle 或我的自定义视图,有多少 space 它可以有。
鉴于 SwiftUI 似乎是自下而上布局,也许这是不可能的,但似乎应该有 some
方式来实现。
没有修饰符 (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)])
}
}
}
所以我实际上找到了一种方法来做到这一点。首先,我尝试在各种配置中将 Spacer 放置在视图周围,试图将它们推到一起,但这没有用。然后我意识到我也许可以使用 .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)
}
}
蓝色视图和所有彩色背景当然只是为了更容易看到。 此代码产生此: