GeometryReader 作用
在 SwiftUI 布局中,parent view 和 child view 的联系是松散的:child view 无法获取到 parent view 的尺寸信息。那么当 child view 的大小位置需要根据 parent view 的大小决定,就无法实现了。比如现在需要一个 rectangle 的大小是 parent view 的一半
struct ContentView: View {
var body: some View {
VStack{
Rectangle()
.frame(width: vstack.width / 2, height: vstack.height / 2)
}
.frame(width: 300, height: 300)
.background(Color.yellow)
}
}
复制代码
想要实现上面的需求,就需要用到 GeometryReader。
GeometryReader是什么
GeometryReader 本身也是一个 view,但是它和其他 view 不同的地方在于它暴露出了一个对象,Rectangle 通过这个对象可以获取到 parent view 的大小。(对于 Rectangle,GeometryReader 就是 parent view)
var body: some View {
VStack{
Rectangle()
.frame(width: 150, height: 150)
}
.frame(width: 300, height: 300)
.background(Color.yellow)
}
复制代码
上面的代码,我们使用 GeomertyReader 替换 VStack
struct ContentView: View {
var body: some View {
GeometryReader{ proxy in
Rectangle()
.frame(width: 150, height: 150)
}
.frame(width: 300, height: 300)
.background(Color.yellow)
}
}
复制代码
对比两个代码,唯一的区别在于 GeometryReader 暴露出了 proxy 对象(proxy 类型为 GeometryProxy,这个对理解文章影响不大)。这里我对于 swift 不熟练的同学再仔细讲下:
VStack和GeometryReader两个结构体的init方法都需要一个闭包函数做参数。GeometryReader的init的参数:闭包函数,这个闭包函数本身也需要一个 proxy 对象做参数。
那么,从暴露出来的 proxy 对象中,我们可以获取到 GeometryReader view 的大小。
struct ContentView: View {
var body: some View {
GeometryReader{ proxy in
Rectangle()
.frame(width: proxy.size.width / 2, height: proxy.size.height / 2) //这里是重点
}
.frame(width: 300, height: 300)
.background(Color.yellow)
}
}
复制代码
上面的代码中,通过 proxy.size 可以获取到 GeometryReader 的 size。
进一步扩展,我们可以把 GeometryReader 放入 VStack 中
struct ContentView: View {
var body: some View {
VStack {
GeometryReader{ proxy in
Rectangle()
.frame(width: proxy.size.width / 2, height: proxy.size.height / 2)
}
}.frame(width: 300, height: 300)
.background(Color.yellow)
}
}
复制代码
这个代码和最开始的代码相比,只是在 VStack 和 Rectangle 之间引入 GeometryReader,之后利用 proxy 对象提供的 size 就可以获取到 VStack的大小,Rectangle 就可以设置成 VStack 大小的一半了。
上面有一个细节忽略了:仔细看代码,Rectangle 从 proxy 中获取到的 size 应该是 GeometryReader 的大小,但是由于 GeometryReader 和 VStack 的大小是一样的,所以就等同于获取到 VStack 的大小。

到这里,我们知道了可以通过 GeometryReader 的 proxy 获取到 parent view 的大小。除了获取到 parent view 的大小,child view 还可以获取到自己在 parent view 区域内的坐标,也就是「我在爸爸的哪个地方」。看下面代码:
struct ContentView: View {
var body: some View {
VStack {
GeometryReader{ proxy in
Text("I'm in (x: \(proxy.frame(in: .local).minX), y: \(proxy.frame(in: .local).minY))")
.font(.title2)
}
}
.frame(height: 200)
.background(Color.yellow)
}
}
复制代码
运行结果如下:

proxy 提供了 frame(in:) 方法,通过这个方法的返回值,Rectangle 可以知道自己在 VStack 中的坐标。(同样,这里 Text 实际 parent view 是 GeometryReader,不过 GeometryReader 和 VStack 大小位置一样,这里就直接这么说了)。
VStack 区域是黄色部分,以左上角为原点建立坐标系,看到 Text 正好在黄色区域的左上角,起始坐标是 (0, 0),这和 frame(in:) 的返回值验证上了。
注意到 frame(in:) 有一个参数,并且我们上面传递的是 .local,实际上 frame(in:) 可以传递 3 个不同的参数:
frame(in: .local),这个参数正如上面所演示的,child view 可以获取到「以 parent view 区域为坐标系的位置」。frame(in: .global),使用这个参数会返回 child view 在整个屏幕的位置。frame(in: .named()),自定义坐标参考系,返回以自定义坐标系的位置。
其中 1 和 2 还能理解,但是 3 是什么意思?这么想一下:现在使用 .local 和 .global 可以获取在 parent view 和 整个屏幕中的位置,那如果希望 child view 获取在「grandfather view」的位置呢?这个时候就可以使用 frame(in: .named()) 了。具体作用如下:
- 在 child view 的任意直系祖先中使用
.coordinateSpace(name: "custom")自定义坐标系。 - child view 中使用
proxy.frame(in :.named("custom"))就可以获取到在该坐标系中的位置。
具体代码如下
struct ContentView: View {
var body: some View {
HStack{
VStack {
VStack{
GeometryReader{proxy in
Text("I'm in (x: \(Int(proxy.frame(in: .named("custom")).minX)), y: \(Int(proxy.frame(in: .named("custom")).minY)))")
}
}.frame(width: 200, height: 200)
.background(Color.yellow)
}
.frame(width: 300, height: 300)
.background(Color.blue)
.coordinateSpace(name: "custom") //自定义一个坐标系
}
}
}
复制代码
运行可以看到,Text 坐标是 (50,50),这个坐标正是相对蓝色区域的坐标系,蓝色区域为 HStack,是 Text 的 grandfather view。

但是要注意一点:frame(in: .named()) 一定是 child view 的直系祖先,类似叔叔之类的是不行的,获取到的坐标是 .global 类型的。代码如下
struct ContentView: View {
var body: some View {
HStack{
VStack{
}.frame(width: 150, height: 150)
.background(Color.blue)
.coordinateSpace(name: "custom")
VStack{
GeometryReader{proxy in
Text("I'm in (x: \(Int(proxy.frame(in: .global).minX)), y: \(Int(proxy.frame(in: .named("custom")).minY)))")
}
}.frame(width: 200, height: 200)
.background(Color.yellow)
}
}
}
复制代码
总结一下:
- 使用
GeometryReader暴露出来的proxy.width && proxy.height可以获取到 parent view 的大小。 - 使用
GeometryReader暴露出来的proxy.frame(in:)方法可以获取 child view 的位置。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)