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

u0njafvf  于 2023-02-28  发布在  Swift
关注(0)|答案(3)|浏览(299)

我需要在SwiftUI中获得渲染视图的宽度,这显然不是那么容易。
我的看法是,我需要一个返回视图维度的函数,就这么简单。

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

mnemlml81#

获取视图维度的唯一可用机制是GeometryReader,该视图由SwiftUI自动调整大小。GeometryReader是一个代理视图,它返回呈现视图的容器的维度。

struct SomeView: View {
    
    @State var size: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("VStack width: \(size.width)")
            Text("VStack height: \(size.height)")
            
            GeometryReader { proxy in
                HStack {} // just an empty container to triggers the onAppear
                    .onAppear {
                        size = proxy.size
                    }
            }
        }
        
    }
}

打印尺寸为VStack的尺寸。

如何获取视图的维度

现在我们知道了GeometryReader为我们提供了容器的大小,接下来通常的问题是:我如何使用它来获得特定视图的大小?
要做到这一点,我们需要将几何阅读器移动到目标视图的下一层。怎么做?我们可以添加一个空的背景来获取目标视图的大小,并将此信息发送回一个Binding
让我们创建一个SizeCalculatorViewModifier,这样我们就可以在每个视图上使用此功能:

struct SizeCalculator: ViewModifier {
    
    @Binding var size: CGSize
    
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    Color.clear // we just want the reader to get triggered, so let's use an empty color
                        .onAppear {
                            size = proxy.size
                        }
                }
            )
    }
}

extension View {
    func saveSize(in size: Binding<CGSize>) -> some View {
        modifier(SizeCalculator(size: size))
    }
}

SizeCalculator的任务是添加一个GeometryReader作为目标视图的背景,在SwiftUI渲染content之后,它会将大小发送回Binding
用法:

struct SomeView: View {
    
    @State var size: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("text width: \(size.width)")
            Text("text height: \(size.height)")
            
            Text("hello")
                .saveSize(in: $size)
        }
        
    }
}

9njqaruj

9njqaruj2#

获取子视图的尺寸是任务的第一部分。将尺寸值冒泡是第二部分。GeometryReader获取父视图的尺寸,这可能不是您想要的。要获取所讨论的子视图的尺寸,我们可以在具有实际大小的子视图上调用修饰符,例如.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)
        }
    }
}
kyks70gy

kyks70gy3#

使用GeometryReader有一个更简单的方法来获取视图的宽度,你需要创建一个状态变量来存储宽度,然后用GeometryReader包围所需的视图,并将宽度值设置为该宽度内的几何图形。

struct ContentView: View {
    @State var width: CGFloat = 0.00 // this variable stores the width we want to get
    
    var body: some View {
        VStack(alignment: .leading) {
            GeometryReader { geometry in
                Text(timer.name)
                    .font(.largeTitle)
                    .fontWeight(.heavy)
                    .onAppear {
                        self.width = geometry.size.width
                        print("text width: \(width)") // test
                    }
            } // in this case, we are reading the width of text
            Text(timer.time)
                .font(.largeTitle)
                .fontWeight(.heavy)
                .opacity(0.5)
        }
    }
}

注意,如果目标的视图也改变了,宽度也会改变。如果你想存储它,我建议在其他地方使用let常量。希望能有所帮助!

相关问题