我有一个标题视图,使用edgesIgnoringSafeArea
将其背景扩展到了状态栏下方。为了正确对齐标题视图的内容/子视图,我需要safeAreaInsets
中的GeometryReader
。但是,当使用GeometryReader
时,我的视图不再具有合适的尺寸。
代码,不使用GeometryReader
struct MyView : View {
var body: some View {
VStack(alignment: .leading) {
CustomView()
}
.padding(.horizontal)
.padding(.bottom, 64)
.background(Color.blue)
}
}
预览
使用GeometryReader
struct MyView : View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
CustomView()
}
.padding(.horizontal)
.padding(.top, geometry.safeAreaInsets.top)
.padding(.bottom, 64)
.background(Color.blue)
.fixedSize()
}
}
}
预览
是否可以使用GeometryReader
而不修改基础视图的大小?
答案 0 :(得分:2)
我尝试使用PreviewLayout,我明白了你的意思。但是,我认为这种行为是预期的。 .sizeThatFits的定义是:
在提供容器时,将容器(A)调整为预览(B)的大小。 运行预览的设备(C)的大小。
我插入了一些字母以定义每个部分并使其更加清晰:
A =预览的最终尺寸。
B =您使用.previewLayout()修改的大小。在第一种情况下,它是VStack。但是在第二种情况下,它是GeometryReader。
C =设备屏幕的大小。
两个视图的行为有所不同,因为VStack并不贪婪,只满足需要。另一方面,GeometryReader尝试拥有所有内容,因为它不知道其子级将要使用什么。如果孩子想要少用,它可以做到,但是必须从提供所有东西开始。
也许如果您编辑问题以确切说明您想完成的工作,我可以稍微完善一下答案。
如果您希望GeometryReader报告VStack的大小。您可以将其放在.background修饰符内。但是同样,我不确定目标是什么,所以也许那是不行的。
我写了一篇关于GeometryReader的不同用法的文章。如果有帮助,请点击以下链接:https://swiftui-lab.com/geometryreader-to-the-rescue/
好的,加上您的附加说明,这里有一个可行的解决方案。请注意,预览将不起作用,因为safeInsets报告为零。但是,在模拟器上,它可以正常运行:
您将看到,我使用视图首选项。在任何地方都没有对它们进行解释,但是我目前正在写一篇有关它们的文章,我将很快发布。
它看起来太冗长,但是如果您发现它使用得太频繁,则可以将其封装在自定义修饰符中。
import SwiftUI
struct InsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
typealias Value = CGFloat
}
struct InsetGetter: View {
var body: some View {
GeometryReader { geometry in
return Rectangle().preference(key: InsetPreferenceKey.self, value: geometry.safeAreaInsets.top)
}
}
}
struct ContentView : View {
var body: some View {
MyView()
}
}
struct MyView : View {
@State private var topInset: CGFloat = 0
var body: some View {
VStack {
CustomView(inset: topInset)
.padding(.horizontal)
.padding(.bottom, 64)
.padding(.top, topInset)
.background(Color.blue)
.background(InsetGetter())
.edgesIgnoringSafeArea(.all)
.onPreferenceChange(InsetPreferenceKey.self) { self.topInset = $0 }
Spacer()
}
}
}
struct CustomView: View {
let inset: CGFloat
var body: some View {
VStack {
HStack {
Text("C \(inset)").color(.white).fontWeight(.bold).font(.title)
Spacer()
}
HStack {
Text("A").color(.white)
Text("B").color(.white)
Spacer()
}
}
}
}
答案 1 :(得分:1)
回答标题中的问题:
GeometryReader
包裹在 .overlay()
或 .background()
中。这样做将减轻 GeometryReader 的布局更改影响。视图将正常布局,GeometryReader 将扩展到视图的完整大小并将 geometry
发出到其内容构建器闭包中。例如,此示例通过将 GeometryReader 包裹在叠加层中来呈现一个蓝色矩形,并在矩形高度的 3/4(而不是填充所有可用空间的矩形)处呈现一个“Hello world”文本:< /p>
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.overlay(GeometryReader { geo in
Text("Hello world").padding(.top, geo.size.height * 3 / 4)
})
Spacer()
}
}
通过在 GeometryReader 上设置框架来实现相同效果的另一个示例:
struct MyView : View {
var body: some View {
GeometryReader { geo in
Rectangle().fill(Color.blue)
Text("Hello world").padding(.top, geo.size.height * 3 / 4)
}
.frame(height: 150)
Spacer()
}
}
视图修饰符适用于应用它们之前的任何内容,而不适用于应用之后的任何内容。在 .edgesIgnoringSafeArea(.all)
之后添加的叠加层/背景将尊重安全区域(不参与忽略安全区域)。
此代码在安全区域内呈现“Hello world”,而蓝色矩形忽略安全区域:
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.edgesIgnoringSafeArea(.all)
.overlay(VStack {
Text("Hello world")
Spacer()
})
Spacer()
}
}
将 .edgesIgnoringSafeArea(.all)
应用到背景使 GeometryReader 忽略安全区域:
struct MyView : View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 150)
.overlay(GeometryReader { geo in
VStack {
Text("Hello world")
// No effect, safe area is set to be ignored.
.padding(.top, geo.safeAreaInsets.top)
Spacer()
}
})
.edgesIgnoringSafeArea(.all)
Spacer()
}
}
可以通过添加多个叠加层/背景来组合多个布局。
测量的几何图形将可用于 GeometryReader 的内容。不是父视图或兄弟视图;即使这些值被提取到 State 或 ObservableObject 中。如果发生这种情况,SwiftUI 将发出运行时警告:
struct MyView : View {
@State private var safeAreaInsets = EdgeInsets()
var body: some View {
Text("Hello world")
.edgesIgnoringSafeArea(.all)
.background(GeometryReader(content: set(geometry:)))
.padding(.top, safeAreaInsets.top)
Spacer()
}
private func set(geometry: GeometryProxy) -> some View {
self.safeAreaInsets = geometry.safeAreaInsets
return Color.blue
}
}
答案 2 :(得分:0)
我设法通过将页面主视图包装在GeometryReader
中并将safeAreaInsets
传递到MyView
来解决此问题。由于它是我们希望整个屏幕都显示在主页上的视图,因此尽可能地贪婪是可以的。
答案 3 :(得分:0)
根据您的视图和用例,也可以使用PreviewLayout.fixed(width:height:)
定义固定的预览大小。
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView().previewLayout(.fixed(width: 400, height: 150))
}
}
唯一的缺点可能是无法动态扩展。但仍比完整的设备预览要好,尤其是在使用multiple previews时。