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

问题描述 投票:0回答:4

我需要在

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)
    }
}
swift swiftui frame
4个回答
82
投票

获取视图尺寸(由 SwiftUI 自动调整大小)的唯一可用机制是

GeometryReader
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
的尺寸。

如何获取View的尺寸

现在我们知道

GeometryReader
为我们提供了容器的大小,通常的后续问题是:如何使用它来获取特定视图的大小?

为此,我们需要将几何读取器移动到目标视图下方一级。如何?我们可以添加一个空背景来获取目标视图的大小并将此信息发送回

Binding

让我们创建一个

SizeCalculator
ViewModifier
,以便我们可以在每个视图上使用此功能:

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
作为我们目标视图的背景。 On 出现,所以在 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)
        }
        
    }
}


44
投票

获取子视图的尺寸是任务的第一部分。将维度值冒泡是第二部分。

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)
        }
    }
}

0
投票

有一种更简单的方法可以使用

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 常量。希望有帮助!


0
投票

参考 Giuseppe Sapienza 的答案,通过向 SizeCalculator 添加 .onChange 修饰符

.onChange(of: proxy.size, { oldValue, newValue in
    size = proxy.size
})

ViewModifier 将动态响应正在测量的视图大小的变化。

© www.soinside.com 2019 - 2024. All rights reserved.