【问题标题】:SwiftUI - Using GeometryReader Without Modifying The View SizeSwiftUI - 在不修改视图大小的情况下使用 GeometryReader
【发布时间】:2019-11-13 11:49:52
【问题描述】:

我有一个标题视图,它使用edgesIgnoringSafeArea 将其背景扩展到状态栏下方。为了正确对齐标题​​视图的内容/子视图,我需要来自GeometryReadersafeAreaInsets。但是,当使用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

【问题讨论】:

  • 你的两个例子对我产生了相同的结果。你能发布你的CustomView吗?而且,通过您的屏幕截图,我认为您正在使用 Xcode Previews。你是?目前它们不是很可靠。
  • 它只是一个带有嵌入式HStackVStack。我检查了 Xcode 预览和模拟器。结果相同。在您的预览提供者中试试这个MyView().previewLayout(.sizeThatFits)
  • 我正在更新到 beta 3,但我的电脑不可用。我稍后再试,让你知道..

标签: swift swiftui


【解决方案1】:

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

    }
}

【讨论】:

  • "VStack is not greedy" 这是一个非常有趣的话题,看起来不同的观点要么贪心要么不贪心,但我们能控制它吗?当我们提出自己的观点时,我们甚至可以控制它吗?我根本找不到任何关于此的文档,这很奇怪,它非常基础。
  • 我正在尝试获取safeAreaInset 以便为我的文本添加适当的填充。这个想法是让MyView 背景位于状态栏下方,然后使用safeAreaInset 适当地偏移文本
  • @Gusutafu 是的,你可以控制你想要使用的空间。你甚至可以超越它。主要是使用 .frame() 和 .fixedSize()。如果您想进一步讨论,请给我发送电子邮件或发布新问题,这样我们就不会劫持这个问题。
  • 太棒了,这行得通。然而,解决如此简单的问题似乎是一种如此复杂的方式。
  • 我明白了。你说得对。 GeometryReader 仍然很贪婪.. :-( 删除了解决方案 2..
【解决方案2】:

我设法通过将页面主视图包装在GeometryReader 中并将safeAreaInsets 传递给MyView 来解决这个问题。由于它是我们想要整个屏幕的主页面视图,因此可以尽可能贪婪。

【讨论】:

    【解决方案3】:

    回答标题中的问题:

    • 可以将GeometryReader 包裹在.overlay().background() 中。这样做将减轻 GeometryReader 的布局更改效果。视图将正常布局,GeometryReader 将扩展为视图的完整大小并将geometry 发送到其内容构建器闭包中。
    • 还可以设置 GeometryReader 的框架以阻止其急于扩展。

    例如,此示例通过将 GeometryReader 包裹在叠加层中,在矩形高度的 3/4 处呈现蓝色矩形和“Hello world”文本(而不是矩形填充所有可用空间):

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

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

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

    2

    .edgesIgnoringSafeArea(.all) 应用到背景会使GeometryReader 忽略 SafeArea:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-10
      相关资源
      最近更新 更多