在 SwiftUI 中使用获取视图的宽度

Get width of a view using in SwiftUI

我需要在 SwiftUI 中获取渲染视图的宽度,这显然不是那么容易。

我的看法是,我需要一个 returns 视图维度的函数,就这么简单。

var body: some View {
    VStack(alignment: .leading) {
        Text(timer.name)
            .font(.largeTitle)
            .fontWeight(.heavy)
        Text(timer.time)
            .font(.largeTitle)
            .fontWeight(.heavy)
            .opacity(0.5)
    }
}

获得 View 尺寸的唯一方法是使用 GeometryReader。 reader returns 容器的尺寸。

什么是几何reader?文档说:

A container view that defines its content as a function of its own size and coordinate space. Apple Doc

所以你可以通过这样做得到尺寸:

struct ContentView: View {
    
   @State var frame: CGSize = .zero
    
    var body: some View {
        HStack {
            GeometryReader { (geometry) in
                self.makeView(geometry)
            }
        }
        
    }
    
    func makeView(_ geometry: GeometryProxy) -> some View {
        print(geometry.size.width, geometry.size.height)

        DispatchQueue.main.async { self.frame = geometry.size }

        return Text("Test")
                .frame(width: geometry.size.width)
    }
}

打印的尺寸是HStack内视图容器的尺寸。

您可以使用另一个 GeometryReader 来获取内部尺寸。

但请记住,SwiftUI 是一个声明式框架。所以你应该避免计算视图的尺寸:

阅读更多示例:

获取子视图的尺寸是任务的第一部分。冒泡维度的价值是第二部分。 GeometryReader 获取父视图的 dims,这可能不是您想要的。为了获得相关子视图的亮度,我们可能会在其子视图上调用一个修饰符,该修饰符具有实际大小,例如 .background().overlay()

struct GeometryGetterMod: ViewModifier {
    
    @Binding var rect: CGRect
    
    func body(content: Content) -> some View {
        print(content)
        return GeometryReader { (g) -> Color in // (g) -> Content in - is what it could be, but it doesn't work
            DispatchQueue.main.async { // to avoid warning
                self.rect = g.frame(in: .global)
            }
            return Color.clear // return content - doesn't work
        }
    }
}

struct ContentView: View {
    @State private var rect1 = CGRect()
    var body: some View {
        let t = HStack {
            // make two texts equal width, for example
            // this is not a good way to achieve this, just for demo
            Text("Long text").overlay(Color.clear.modifier(GeometryGetterMod(rect: $rect1)))
            // You can then use rect in other places of your view:

            Text("text").frame(width: rect1.width, height: rect1.height).background(Color.green)
            Text("text").background(Color.yellow)
        }
        print(rect1)
        return t
    }
}

这是另一种获取和处理当前视图大小的便捷方式:readSize 函数。

extension View {
  func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
    background(
      GeometryReader { geometryProxy in
        Color.clear
          .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
      }
    )
    .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
  }
}

private struct SizePreferenceKey: PreferenceKey {
  static var defaultValue: CGSize = .zero
  static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

用法:

struct ContentView: View {
    @State private var commonSize = CGSize()
    var body: some View {
        VStack {
        Text("Hello, world!")
            .padding()
            .border(.yellow, width: 1)
            .readSize { textSize in
                commonSize = textSize
            }
        Rectangle()
                .foregroundColor(.yellow)
            .frame(width: commonSize.width, height: commonSize.height)
        }
    }
}