SwiftUI

SwiftUI的 GeometryReader

SwiftUI的 GeometryReader 允许我们根据其自身的大小和坐标来确定视图的大小和坐标,这是在SwiftUI中创建一些出色的效果的关键。

在使用GeometryReader时,您应始终牢记SwiftUI的三步布局系统:父级为子级提供了一个尺寸,子级使用该尺寸确定自己的尺寸,父级使用该尺寸适当地定位子级。

在其最基本的用法中,GeometryReader的作用是让我们读取父级提供的大小,然后使用该大小来操纵我们的视图。例如,我们可以使用GeometryReader使文本视图具有所有可用空间的90%,而不管其内容是什么:

struct ContentView: View {
    var body: some View {
        GeometryReader { geo in
            Text("Hello, World!")
                .frame(width: geo.size.width * 0.9)
                .background(Color.red)
        }
    }
}

传入的那个geo参数是一个GeometryProxy,它包含能够提供的最大的尺寸,已应用的所有安全区域,以及一种用于读取Frame 值的方法。

GeometryReader有一个有趣的副作用,可能一开始会吸引您:返回的视图具有灵活的首选大小,这意味着它将根据需要扩展以占用更多空间。如果将GeometryReader放入VStack中,然后在其下放一些其他文本,则可以看到它的作用,如下所示:

struct ContentView: View {
    var body: some View {
        VStack {
            GeometryReader { geo in
                Text("Hello, World!")
                    .frame(width: geo.size.width * 0.9, height: 40)
                    .background(Color.red)
            }

            Text("More text")
                .background(Color.blue)
        }
    }
}

您会看到“More text”被直接推到屏幕底部,因为GeometryReader占据了所有剩余空间。要查看实际效果,请将background(Color.green)作为修改器添加到GeometryReader中,您将看到它的大小。注意:这是首选大小,而不是绝对大小,这意味着它仍然可以灵活地取决于其父项。

在读取视图Frame时,GeometryProxy提供了frame(in :)方法,而不是简单的属性。这是因为“Frame”的概念包括X坐标和Y坐标,这些坐标孤立地没有任何意义–您是要视图的绝对X坐标还是Y坐标,还是与父视图相比它们的X坐标和Y坐标?

SwiftUI将这些选项称为坐标空间,特别是将这两个称为全局空间(相对于整个屏幕衡量我们的视图空间)和局部空间(相对于其父视图衡量我们的视图空间)。我们还可以通过将ordinateSpace()修饰符附加到视图来创建自定义坐标空间,然后该视图的任何子项都可以读取相对于该坐标空间的Frame。

为了演示坐标空间是如何工作的,我们可以在不同的堆栈中创建一些示例视图,将自定义坐标空间附加到最外面的视图,然后在其中的一个视图中添加onTapGesture,以便它可以全局/局部地打印Frame和使用自定义坐标空间。

尝试如下代码:

struct OuterView: View {
    var body: some View {
        VStack {
            Text("Top")
            InnerView()
                .background(Color.green)
            Text("Bottom")
        }
    }
}

struct InnerView: View {
    var body: some View {
        HStack {
            Text("Left")
            GeometryReader { geo in
                Text("Center")
                    .background(Color.blue)
                    .onTapGesture {
                        print("Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
                        print("Custom center: \(geo.frame(in: .named("Custom")).midX) x \(geo.frame(in: .named("Custom")).midY)")
                        print("Local center: \(geo.frame(in: .local).midX) x \(geo.frame(in: .local).midY)")
                    }
            }
            .background(Color.orange)
            Text("Right")
        }
    }
}

struct ContentView: View {
    var body: some View {
        OuterView()
            .background(Color.red)
            .coordinateSpace(name: "Custom")
    }
}

该代码运行时所获得的输出取决于您所使用的设备,但这是我得到的:

  • Global center: 202.0 x 455.16666666666663
  • Custom center: 202.0 x 411.16666666666663
  • Local center: 164.0 x 378.5

这些尺寸大部分是不同的,因此希望您能看到这些框架如何工作的全部内容:

  • 全局中心X为202表示文本视图的中心距离屏幕左边缘202个点。这并不是定死在屏幕中央,因为“Left”和“Right”标签的尺寸不同。
  • 全局中心Y为455.167表示文本视图的中心距离屏幕顶部边缘455.167点。这并不是定死在屏幕中央,因为顶部的安全区域比底部的安全区域大。
  • 自定义中心X为202表示文本视图的中心距拥有“自定义”坐标空间的任何视图的左边缘202个点,在本例中为OuterView,因为我们将其附加到ContentView中。此数字与全局位置匹配,因为OuterView水平边缘挨到屏幕边缘。
  • 自定义中心Y为411.167,表示文本视图的中心距离OuterView的顶部边缘为411.167点。该值小于全局中心Y,因为OuterView不会延伸到安全区域。
  • 局部中心X为164表示文本视图的中心距离其直接容器(在本例中为GeometryReader)的左边缘164个点。
  • Y的本地中心378.5表示文本视图的中心距离其直接容器(也是GeometryReader)的顶部边缘378.6点。

您要使用哪个坐标空间取决于您要回答的问题:

  • 是否想知道此视图在屏幕上的位置?使用全局空间。
  • 是否想知道此视图相对于其父视图的位置?使用本地空间。
  • 要知道该视图相对于其他视图的位置?使用自定义空间