SwiftUI中GeometryReader与GeometryProxy的理解与使用

SwiftUI中的GeometryReader是一个视图,使用它我们可以很容易地访问父视图的大小和位置,并使用这些信息来创建一个响应式布局,以适应不同的设备和方向。
在本文中,我们将探索使用GeometryReader的好处,并提供一些如何在项目中使用它的示例。

基本使用

GeometryReader的闭包中提供了一个GeometryProxy的变量,用于访问容器视图的大小和空间坐标的代理。

struct GeometryReaderDemo: View {
  var body: some View {
    GeometryReader { geometry in
      VStack(spacing: 10) {
        Text("GeometryReader")
          .font(.title)
        Text("geometry size: \(geometry.size)")
          .font(.subheadline)

        HStack(spacing: 0) {
          Button("Button 1") {}
            .frame(width: geometry.size.width/3, height: 50)
            .background(.orange)
          Button("Button 2") {}
            .frame(width: geometry.size.width/3, height: 50)
            .background(.yellow)
          Button("Button 3") {}
            .frame(width: geometry.size.width/3, height: 50)
            .background(.green)
        }
      }
    }
  }
}

在这里插入图片描述
上面的代码中,讲得到的父视图宽度三分,分别设置给了三个Button。

在比如下面这个,我们可以给GeometryReader设置任意宽度,其内部的圆形直径永远都是GeometryReader宽度的一半,根本不用在调整Circle的尺寸。

在这里插入图片描述
还有一点值得提一下,先看下面这个图:
在这里插入图片描述
上面代码中竖向显示了3个Text,对三个Text的父视图VStack加了颜色,以及GeometryReader也加了背景颜色。很明显能看得出来GeometryReader的尺寸是全屏尺寸,不过这是一个首选大小,而不是绝对大小,具体还是取决于GeometryReader的父视图,我们可以显示地给GeometryReader添加frame修饰符。

另外在GeometryReader内部,所有的组件都是左上角到右下角布局的,这个和我们在UIKit中的坐标系很像。GeometryReaderSwiftUI中的其他组件的布局不太一样。

很多时候GeometryReader中放了Color.clear,因为颜色会自动填充父视图的大小,所以GeometryReader也是其父视图的大小,将这个组合放到ScrollViewbackground修饰符中,可以计算一些偏移量,内容总尺寸什么的,比如在这篇文章中就涉及到了,这里就不过多说明了。

coordinates 理解与使用

除了简单的size属性,GeometryProxy还提供了一个frame(in:)方法,该方法返回视图在指定坐标空间的frame数据。

func frame(in coordinateSpace: CoordinateSpace) -> CGRect

CoordinateSpace主要有三种:

  • 全局坐标空间:相对于视图层次结构根的全局坐标空间。也就是相对于整个屏幕。
  • 局部坐标空间:相对于当前视图的局部坐标空间。。
  • 自定义坐标空间:将coordinateSpace()修饰符附加到视图上来创建自定义坐标空间——该视图的任何子视图都可以读取相对于该坐标空间的frame。

可能不太好理解,看看下面的示例,有助于理解,首先明确一点,frame(in:)方法返回的是调用者GeometryProxy实例对象的frame信息,也就是GeometryReaderframe信息。

  var body: some View {
    // 上面红色 高度100
    VStack(spacing: 0) {
      Color.red
        .frame(height: 100)

      HStack(spacing: 0) {
        // 左侧蓝色 宽度100
        Color.blue
          .frame(width: 100)

        // 中间橘黄色
        VStack(spacing: 0) {
          // GeometryReader 绿色
          GeometryReader { proxy in
            VStack(spacing: 0) {
              Color.green
                .onTapGesture {
                  printFrameMessage(proxy: proxy)
                }
            }
          }
          .background(.orange)
          .padding(50) // GeometryReader四边缩进50.
        }
        .background(Color.orange)
        .coordinateSpace(name: "Custom")

        // 右侧蓝色 宽度100
        Color.blue
          .frame(width: 100)
      }

      // 下面红色 高度100
      Color.red
        .frame(height: 100)
    }
  }

  private func printFrameMessage(proxy: GeometryProxy) {
    print("Screen size: \(UIScreen.main.bounds.size)")
    print("Global center: \(proxy.frame(in: .global).midX) x \(proxy.frame(in: .global).midY)")
    print("Custom center: \(proxy.frame(in: .named("Custom")).midX) x \(proxy.frame(in: .named("Custom")).midY)")
    print("Local center: \(proxy.frame(in: .local).midX) x \(proxy.frame(in: .local).midY)")
  }

在这里插入图片描述
上面代码中,绿色部分为GeometryReader,并添加了点击事件,点击后显示出该GeometryReader相对于三个坐标空间的中心点坐标信息。

整个屏幕为global坐标空间。
橘黄色部分为自定义坐标空间,代码中添加了.coordinateSpace(name: "Custom")
绿色部分为local坐标空间。

关于各颜色块的尺寸代码中有标注。

点击绿色区域后,打印出的信息如下,各数据已在上图中有标出,一目了然。

Screen size: (393.0, 852.0)
Global center: 196.5 x 438.5
Custom center: 96.5 x 279.5
Local center: 46.5 x 229.5

写在最后

本文主要介绍了GeometryReader的用法,以及使用GeometryProxy读取framecoordinates数据,介绍了三种坐标系,文中所有代码都测试过,如果有不正确的地方,还望大家指正。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SwiftUI ,可以使用 `List` 或 `ScrollView` 来显示可滚动的列表或视图。为了添加下拉刷新功能,可以使用 `UIRefreshControl`。 首先,需要创建一个 `UIRefreshControl` 的实例,然后将其添加到 `List` 或 `ScrollView` 。在用户下拉列表时,`UIRefreshControl` 会触发一个动作。在该动作,可以执行更新数据的操作,并结束下拉刷新。 下面是一个示例代码,演示如何在 SwiftUI 使用 `UIRefreshControl` 实现下拉刷新: ```swift import SwiftUI struct ContentView: View { @State private var isRefreshing = false @State private var data = ["Item 1", "Item 2", "Item 3"] var body: some View { List(data, id: \.self) { item in Text(item) } .onPullToRefresh(isRefreshing: $isRefreshing) { // Simulate a network request DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.data = ["New Item 1", "New Item 2", "New Item 3"] self.isRefreshing = false } } } } extension View { func onPullToRefresh(isRefreshing: Binding<Bool>, action: @escaping () -> Void) -> some View { ModifiedContent(content: self, modifier: PullToRefresh(isRefreshing: isRefreshing, action: action)) } } struct PullToRefresh: ViewModifier { @Binding var isRefreshing: Bool var action: () -> Void func body(content: Content) -> some View { content .overlay( GeometryReader { geometry in VStack { if self.isRefreshing { ActivityIndicator(isAnimating: .constant(true), style: .medium) } else { Color.clear } } .frame(width: geometry.size.width, height: 60, alignment: .center) .offset(y: -60) }, alignment: .bottom ) .onAppear { let refreshControl = UIRefreshControl() refreshControl.addTarget(self, action: #selector(self.onRefresh), for: .valueChanged) UITableView.appearance().refreshControl = refreshControl } } @objc private func onRefresh() { isRefreshing = true action() } } struct ActivityIndicator: UIViewRepresentable { @Binding var isAnimating: Bool let style: UIActivityIndicatorView.Style func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView { UIActivityIndicatorView(style: style) } func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) { isAnimating ? uiView.startAnimating() : uiView.stopAnimating() } } ``` 在该示例,我们使用 `List` 显示一个简单的字符串列表。我们添加了一个名为 `onPullToRefresh` 的自定义修饰符,用于在 `List` 上添加下拉刷新功能。该修饰符接受两个参数:一个布尔绑定,用于控制刷新控件的状态;一个闭包,用于执行更新数据的操作。 在该示例,我们使用 `GeometryReader` 将一个 `ActivityIndicatorView` 添加到列表头部。当用户下拉列表时,`UIRefreshControl` 会触发一个动作,在该动作,我们将布尔绑定设置为 `true`,然后执行更新数据的操作。当数据更新完成后,我们将布尔绑定设置为 `false`,结束下拉刷新。 请注意,由于 SwiftUI 的 `List` 是基于 `UITableView` 实现的,因此我们需要使用 `UITableView` 的 `refreshControl` 属性来添加 `UIRefreshControl`。在 `onAppear` ,我们设置了 `UITableView` 的 `refreshControl` 属性,将其与我们创建的 `UIRefreshControl` 实例关联起来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值