SwiftUI-使用GeometryReader而不修改视图大小

时间:2019-07-02 19:44:11

标签: swift swiftui

我有一个标题视图,使用edgesIgnoringSafeArea将其背景扩展到了状态栏下方。为了正确对齐标题​​视图的内容/子视图,我需要safeAreaInsets中的GeometryReader。但是,当使用GeometryReader时,我的视图不再具有合适的尺寸。

代码,不使用GeometryReader

struct MyView : View {
    var body: some View {
        VStack(alignment: .leading) {
            CustomView()
        }
        .padding(.horizontal)
        .padding(.bottom, 64)
        .background(Color.blue)
    }
}

预览

Expected

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

预览

enter image description here

是否可以使用GeometryReader而不修改基础视图的大小?

4 个答案:

答案 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报告为零。但是,在模拟器上,它可以正常运行:

您将看到,我使用视图首选项。在任何地方都没有对它们进行解释,但是我目前正在写一篇有关它们的文章,我将很快发布。

它看起来太冗长,但是如果您发现它使用得太频繁,则可以将其封装在自定义修饰符中。

enter image description here

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 的框架以阻止其急于扩展。

例如,此示例通过将 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()
    }
}

Render with "Hello world" on 3/4th the height of a blue rectangle.

但是,有一些警告/不是很明显的行为

1

视图修饰符适用于应用它们之前的任何内容,而不适用于应用之后的任何内容。在 .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()
    }
}

Render of "Hello world" inside the safe area.

2

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

Render of "Hello world" ignoring the safe area.

可以通过添加多个叠加层/背景来组合多个布局。

3

测量的几何图形将可用于 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
    }
}

Runtime warning "Modifying state during view update, this will cause undefined behavior."

答案 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时。