添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

【SwiftUI基础篇】3 GeometryReader的用法详解

Kwok 发表于:2021-04-01 11:01:55 点击:67 评论: 0

如果非要把GeometryReader 翻译成中文,根据我个人的理解可以翻译为:几何及坐标读取者(几何参数读取), GeometryReader 主要用于获取父视图的大小和位置,从而让子视图可以随父视图的大小变化而灵活变化其形态和内容,它的输入类型为 (GeometryProxy) -> Content,下面代码里的 GeometryProxy(也就是这里的 proxy ),它是一个返回访问容器视图的大小和坐标空间(用于锚点解析)的代理。其有 2 个属性(safe Area Insets、size)和 1 个方法(frame)最常用的是 size: CGSzie,表示父视图的大小。

GeometryProxy属性1:var safe Area Insets: Edge Insets 容器视图的安全区域插入。

GeometryProxy属性2:var size: CGSize 容器视图的大小。

GeometryProxy方法:func frame(in: Coordinate Space) -> CGRect 返回容器视图的边界矩形,转换为一个定义的坐标空间。

其方法中的 CoordinateSpace 是一个枚举值:global、local、named(Any Hashable)

SwiftUI布局3大法则

1. 父级Vew为子级的View提供一个建议尺寸值(size)

2. 子级的View根据这个建议的尺寸(size)和自身的特点返回一个尺寸值(size)

3. 父级View根据子级View返回的尺寸值(size)在坐标空间中布局。

而GeometryReader的作用就是:

1. 能够获取到父级View给出的建议的尺寸值(size)

2. 可以通过frame(in:)函数,传入一个相对坐标系,可以获取GeometryReader相对某个view的frame

3. 在某些特殊情况下,比如ScrollView中加了GeometryReader,如果使用了frame(in:)函数,返回的值会根据view的位置实时的自动变化,利用此技术可以实现复杂UI功能(这里有演示代码: http://www.55mx.com/ios/115.html )。

一、GeometryReader的基本用法:

var body: some View {
    Rectangle()
        .stroke(lineWidth: 5)
        .frame(width: 200, height: 200, alignment: .center)
        .overlay(subView)//用subView子视图覆盖在本视图之上
//定义一个subView子视图
var subView: some View {
    GeometryReader { proxy in
        Rectangle()//定义一个矩形
            .foregroundColor(.blue)//设置颜色
            //定义矩形的大小为父视图的一半
            .frame(width: proxy.size.width / 2, height: proxy.size.height / 2)
            //让矩形根据坐标偏移
            .offset(x: proxy.size.width / 2.3,y: proxy.size.height / 9)

GeometryProxy 的另一个属性是 safeAreaInsets, 用来表示当前不可用的空间有多少。例如 safeAreaInsets.top 表示了上方刘海部分加上导航栏的高度safeAreaInsets.bottom 表示下方圆角部分的高度。

GeometryReader { proxy in
    List {
        Text("在模拟器中翻转屏幕,来观察 4 条边上”安全区域“的数值变化").bold()
        Text("安全区域.顶: (proxy.safeAreaInsets.top)")
        Text("安全区域.底: (proxy.safeAreaInsets.bottom)")
        Text("安全区域.左: (proxy.safeAreaInsets.leading)")
        Text("安全区域.右: (proxy.safeAreaInsets.trailing)")

除了上述的属性,GeometryProxy 还有 frame 这个方法会返回一个 CGRect,可以用来获取父视图相对某一坐标的位置。下面的例子中 .local 获得的就是相对父视图的坐标系,所以 x, y 都是 0,红色方块和黑框重叠而.global 就是父视图相对整体的位置,所以蓝方块也向右下进行了平移。

GeometryReader { geometry in
    ZStack {
        Rectangle()
            //path将此形状描述为矩形参照系内的路径。
            .path(in: geometry.frame(in: .local))
            //校正描述这个形状的参考系为相对父视图的坐标系。
            .foregroundColor(.red)
        Rectangle()
            //参考系为父视图相对整体的位置。
            .path(in: geometry.frame(in: .global))
            .foregroundColor(.blue)

二、在滚动视图里使用GeometryReader:

ScrollView(.horizontal){
    HStack{
        ForEach(0..<50){ i in
            GeometryReader{ proxy in
                let x = Int(proxy.frame(in:.global).origin.x)//全局的x坐标位置
                Text("X:(x)")//当滚动视图时X值会随之改变
                    .bold()
                    .font(.largeTitle)
                    .foregroundColor(.white)
                    //大概会偏移到中间位置
                    .offset(x: proxy.size.width / 3, y: proxy.size.height / 3)
            .frame(width: 200, height: 200)
            .background(Color.orange)
            .padding()

 如果在上面的Text视图后面加上下面的代码就变成了跳舞的文字:

.rotation3DEffect(.degrees(-Double(proxy.frame(in:.global).minX)),axis: (x:0, y:1, z:0))

三、利用 GeometryReader 实现3D显示效果

首先定义一批颜色演示:

let colors: [Color] = [.red, .green, .blue, .orange, .pink, .purple, .yellow]

然后在视图里使用下面的代码:

GeometryReader { fullView in
    ScrollView(.horizontal, showsIndicators: false) {
        HStack {
            ForEach(0..<50) { index in
                GeometryReader { geo in
                    Rectangle()
                        .fill(colors[index % colors.count])
                        //用颜色或渐变填充这个形状。
                        .frame(height: 150)
                        //下面的算法是效果的关键
                        .rotation3DEffect(.degrees(-Double(geo.frame(in: .global).midX - fullView.size.width / 2) / 10), axis: (x: 0, y: 1, z: 0))
                .frame(width: 150)
        .padding(.horizontal, (fullView.size.width - 150) / 2)

如果要在VStack里应用螺旋样式的旋转效果,请将其rotation3DEffect()直接放在修改器下方:

.rotation3DEffect(.degrees(Double(geo.frame(in: .global).minY) / 5), axis: (x: 0, y: 1, z: 0))

四、使用GeometryReader调整图像大小以适合屏幕

当我们Image在SwiftUI中创建视图时,它将根据其内容的尺寸自动调整自身大小。因此,如果图片为1000x500,则Image视图也将为1000x500。有时您将希望以较小的尺寸显示图像,以及如何使用以下方法使图像适合用户的屏幕宽度:

GeometryReader { geo in
    Image(systemName: "calendar.circle.fill")